diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index d1ea1fa6..2be0b0f1 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -25,12 +25,13 @@ jobs: - {os: ubuntu-22.04, r: 'oldrel'} steps: - - name: Checkout E2E Directory - # Only checkout the necessary files; the DESCRIPTION file breaks test_r() due to Issue #461 - uses: Bhacaz/checkout-files@v2 + - name: Checkout E2E directory + uses: actions/checkout@v4 with: - files: tests/e2e - branch: ${{ github.head_ref || github.ref_name }} + # Only checkout the necessary files; the DESCRIPTION file breaks test_r(): + # https://github.com/Appsilon/rhino/issues/461 + sparse-checkout: /tests/e2e/ + sparse-checkout-cone-mode: false - name: Install R uses: r-lib/actions/setup-r@v2 @@ -64,6 +65,13 @@ jobs: cd RhinoApp Rscript ../test-dependencies.R + - name: Node.js commands should respect RHINO_NPM + # Skip this test on Windows because it requires a Unix shell. + if: runner.os != 'Windows' + run: | + cd RhinoApp + Rscript ../test-custom-npm.R + - name: lint_r() should detect lint errors in R scripts if: always() run: | diff --git a/NEWS.md b/NEWS.md index 33cf765c..b1d65880 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,7 +1,6 @@ -# rhino (development-next) +# rhino (development version) - -# rhino (development) +# [rhino 1.6.0](https://github.com/Appsilon/rhino/releases/tag/v1.6.0) 1. `pkg_install` supports installation from local sources, GitHub, and Bioconductor. 2. Improve Rhino CI (use latest versions and make better use of actions). @@ -13,6 +12,8 @@ * `lint_sass()` now uses `stylelint` 14.16 (the last major version supporting stylistic rules) * Upgrade all remaining Node.js dependencies to latest versions and fix vulnerabilities. * The minimum supported Node.js version is now 16. +4. Introduce `RHINO_NPM` environment variable +to allow using `npm` alternatives like `bun` and `pnpm`. # [rhino 1.5.0](https://github.com/Appsilon/rhino/releases/tag/v1.5.0) diff --git a/R/node.R b/R/node.R index 2263eb64..7f0fbae9 100644 --- a/R/node.R +++ b/R/node.R @@ -2,33 +2,36 @@ node_path <- function(...) { fs::path(".rhino", ...) } -add_node <- function(clean = FALSE) { - if (clean && fs::dir_exists(node_path())) { - fs::dir_delete(node_path()) +# Run `npm` or an alternative command specified by `RHINO_NPM`. +# If needed, copy over Node.js template and install dependencies. +npm <- function(...) { + npm_command <- Sys.getenv("RHINO_NPM", "npm") + check_system_dependency( + cmd = npm_command, + dependency_name = ifelse(npm_command == "npm", "Node.js", npm_command), + documentation_url = "https://go.appsilon.com/rhino-system-dependencies" + ) + node_init(npm_command) + node_run(npm_command, ...) +} + +node_init <- function(npm_command) { + if (!fs::dir_exists(node_path())) { + cli::cli_alert_info("Initializing Node.js directory...") + copy_template("node", node_path()) + } + if (!fs::dir_exists(node_path("node_modules"))) { + cli::cli_alert_info("Installing Node.js packages with {npm_command}...") + node_run(npm_command, "install", "--no-audit", "--no-fund") } - copy_template("node", node_path()) } -# Run `npm` command (assume node directory already exists in the project). -npm_raw <- function(..., status_ok = 0) { +# Run the specified command in Node.js directory (assume it already exists). +node_run <- function(command, ..., status_ok = 0) { withr::with_dir(node_path(), { - status <- system2(command = "npm", args = c(...)) + status <- system2(command = command, args = c(...)) }) if (status != status_ok) { - cli::cli_abort("System command 'npm' exited with status {status}.") - } -} - -# Run `npm` command (create node directory in the project if needed). -npm <- function(...) { - check_system_dependency( - cmd = "node", - dependency_name = "Node.js", - documentation_url = "https://go.appsilon.com/rhino-system-dependencies" - ) - if (!fs::dir_exists(node_path())) { - add_node() - npm_raw("install", "--no-audit", "--no-fund") + cli::cli_abort("System command '{command}' exited with status {status}.") } - npm_raw(...) } diff --git a/inst/WORDLIST b/inst/WORDLIST index 70df777b..c89b7b65 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -1,3 +1,5 @@ +Addin +Addins Appsilon Boxifying ESLint @@ -13,9 +15,11 @@ Renv Renviron Rhinoverse Rprofile +Rstudio SDK Stylelint UI +Webpack blogpost conf config @@ -41,6 +45,7 @@ nodejs npm nvm overridable +pnpm preconfigured renv roxygen @@ -54,5 +59,3 @@ unintuitive usethis webpack yml -Addin -Addins diff --git a/tests/e2e/test-custom-npm.R b/tests/e2e/test-custom-npm.R new file mode 100644 index 00000000..91f43a44 --- /dev/null +++ b/tests/e2e/test-custom-npm.R @@ -0,0 +1,22 @@ +local({ + tmp <- withr::local_tempdir() + wrapper_path <- fs::path(tmp, "wrapper") + touch_path <- fs::path(tmp, "it_works") + + # Prepare a wrapper script which creates an "it_works" file and runs npm. + fs::file_create(wrapper_path, mode = "u=rwx") + writeLines( + c( + "#!/bin/sh", + paste("touch", touch_path), + 'exec npm "$@"' + ), + wrapper_path + ) + + # Use the wrapper script instead of npm. + withr::local_envvar(RHINO_NPM = wrapper_path) + rhino:::npm("--version") + + testthat::expect_true(fs::file_exists(touch_path)) +}) diff --git a/vignettes/explanation/node-js-javascript-and-sass-tools.Rmd b/vignettes/explanation/node-js-javascript-and-sass-tools.Rmd index e9044fd9..b896e43d 100644 --- a/vignettes/explanation/node-js-javascript-and-sass-tools.Rmd +++ b/vignettes/explanation/node-js-javascript-and-sass-tools.Rmd @@ -13,7 +13,13 @@ vignette: > can execute JavaScript code outside a web browser. It is used widely for web development. Its package manager, [npm](https://docs.npmjs.com/about-npm), makes it easy to install -virtually any JavaScript library. +virtually any JavaScript library. You can use other package managers such as +[bun](https://bun.sh) and [pnpm](https://pnpm.io/) that are compatible with +`npm`. + +To switch from the default npm usage, set a global environment variable named +`RHINO_NPM`. For instance, if you want to use `bun` instead of `npm`, +add `export RHINO_NPM=bun` to your shell startup file (e.g. `.bashrc`). Rhino uses Node.js to provide state of the art tools for working with JavaScript and Sass. The following functions require Node.js to work: @@ -26,7 +32,7 @@ JavaScript and Sass. The following functions require Node.js to work: ### Node directory -Under the hood Rhino will create a `.rhino/node` directory in your +Under the hood Rhino will create a `.rhino` directory in your project to store the specific libraries needed by these tools. This directory is git-ignored by default and safe to remove.