Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add development mode - rhino::dev() #609

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Imports:
glue,
lintr (>= 3.0.0),
logger,
processx,
purrr,
renv,
rstudioapi,
Expand Down
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
export(app)
export(build_js)
export(build_sass)
export(dev)
export(diagnostics)
export(format_js)
export(format_r)
Expand Down
81 changes: 65 additions & 16 deletions R/node.R
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,85 @@ node_path <- function(...) {
fs::path(".rhino", ...)
}

npm_command <- function() {
Sys.getenv("RHINO_NPM", "npm")
}

# Run a script defined in `package.json`.
npm_run <- function(...) {
# Use `--silent` to prevent `npm` from echoing the command it runs.
# The output of the script itself will still be displayed.
npm("--silent", "run", ...)
}

# 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 <- node_check()
if (!node$status_ok) {
node_missing(abort = TRUE)
}
node_init()
node_run(..., wd = node_path())
}

node_check <- function() {
version <- tryCatch(
node_run("--version", stdout = "|")$stdout,
error = function(e) NULL
)
list(
status_ok = !is.null(version),
diagnostic_info = paste(npm_command(), ifelse(is.null(version), "failed", trimws(version)))
)
node_init(npm_command)
node_run(npm_command, ...)
}

node_init <- function(npm_command) {
node_missing <- function(info = NULL, abort = FALSE) {
docs_url <- "https://go.appsilon.com/rhino-system-dependencies" # nolint object_usage_linter
msg <- c(
"!" = "Failed to run system command {.code {npm_command()}}.",
" " = "Do you have Node.js installed? Read more: {.url {docs_url}}",
"i" = info
)
if (abort) {
cli::cli_abort(msg)
} else {
cli::cli_bullets(msg)
}
}

node_init <- function() {
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")
cli::cli_alert_info("Installing Node.js packages with {.code {npm_command()}}...")
node_run("install", "--no-audit", "--no-fund", wd = node_path())
}
}

# 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 = command, args = c(...))
})
if (status != status_ok) {
cli::cli_abort("System command '{command}' exited with status {status}.")
node_run <- function(..., stdout = NULL, wd = NULL, background = FALSE) {
if (background) {
run <- processx::process$new
} else {
run <- processx::run
}

# Workaround: {processx} cannot find `npm` on Windows, but it works with a shell.
if (.Platform$OS.type == "windows") {
command <- "cmd"
args <- rlang::chr("/c", npm_command(), ...)
} else {
command <- npm_command()
args <- rlang::chr(...)
}

run(
command = command,
args = args,
stdout = stdout,
wd = wd
)
}
33 changes: 2 additions & 31 deletions R/rhino.R
Original file line number Diff line number Diff line change
Expand Up @@ -48,35 +48,6 @@ copy_rproj <- function() {
)
}

system_cmd_version <- function(cmd, throw_error = FALSE) {
tryCatch(
system2(cmd, "--version", stdout = TRUE, stderr = TRUE),
error = function(e) {
if (isTRUE(throw_error)) cli::cli_abort(e)

e$message
}
)
}

check_system_dependency <- function(
cmd,
dependency_name,
documentation_url,
additional_message = NULL
) {
message <- c(
glue::glue("Do you have {dependency_name} installed?"),
glue::glue("Check {documentation_url} for details."),
additional_message
)

tryCatch(
system_cmd_version(cmd, TRUE),
error = function(e) cli::cli_abort(message)
)
}

#' Print diagnostics
#'
#' Prints information which can be useful for diagnosing issues with Rhino.
Expand All @@ -93,7 +64,7 @@ diagnostics <- function() {
writeLines(c(
paste(Sys.info()[c("sysname", "release", "version")], collapse = " "),
R.version.string,
paste("rhino:", utils::packageVersion("rhino")),
paste("node:", system_cmd_version("node"))
paste("rhino", utils::packageVersion("rhino")),
node_check()$diagnostic_info
))
}
83 changes: 69 additions & 14 deletions R/tools.R
Original file line number Diff line number Diff line change
@@ -1,3 +1,58 @@
#' Development mode
#'
#' Run application in development mode with automatic rebuilding and reloading.
#'
#' This function will launch the Shiny app in
#' [development mode](https://shiny.posit.co/r/reference/shiny/latest/devmode.html)
#' (as if `options(shiny.devmode = TRUE)` was set).
#' The app will be automatically reloaded whenever the sources change.
#'
#' Additionally, Rhino will automatically rebuild JavaScript and Sass in the background.
#' Please note that this feature requires Node.js.
#'
#' @param build_js Boolean. Rebuild JavaScript automatically in the background?
#' @param build_sass Boolean. Rebuild Sass automatically in the background?
#' @param ... Additional arguments passed to `shiny::runApp()`.
#' @return None. This function is called for side effects.
#'
#' @export
dev <- function(build_js = TRUE, build_sass = TRUE, ...) {
proc <- dev_build(build_js, build_sass)
if (!is.null(proc)) on.exit(proc$kill())
shiny::with_devmode(TRUE, shiny::runApp(...))
}

dev_build <- function(build_js, build_sass) {
if (!build_js && !build_sass) return()

node <- node_check()
if (!node$status_ok) {
node_missing(node$npm_command, info = "JavaScript and Sass won't be automatically rebuilt.")
return()
}

if (build_sass) {
config <- read_config()
if (config$sass != "node") {
build_sass <- FALSE
cli::cli_bullets(c(
"!" = "Sass won't be automatically rebuilt.",
"i" = "Use {.code sass: node} configuration in {.file rhino.yml} to enable it."
))
}
}

# Is there is anything to do? Check again - building Sass might have been disabled.
if (!build_js && !build_sass) return()

npm_run(
"concurrently", "--",
if (build_js) "npm:build-js -- --watch",
if (build_sass) "npm:build-sass -- --watch",
background = TRUE
)
}

#' Run R unit tests
#'
#' Uses the `{testhat}` package to run all unit tests in `tests/testthat` directory.
Expand Down Expand Up @@ -234,9 +289,9 @@ format_r <- function(paths, exclude_files = NULL) {
#' @export
build_js <- function(watch = FALSE) {
if (watch) {
npm("run", "build-js", "--", "--watch", status_ok = 2)
npm_run("build-js", "--", "--watch")
} else {
npm("run", "build-js")
npm_run("build-js")
}
}

Expand Down Expand Up @@ -273,9 +328,9 @@ build_js <- function(watch = FALSE) {
# nolint end
lint_js <- function(fix = FALSE) {
if (fix) {
npm("run", "lint-js", "--", "--fix")
npm_run("lint-js", "--", "--fix")
} else {
npm("run", "lint-js")
npm_run("lint-js")
}
}

Expand All @@ -296,9 +351,9 @@ lint_js <- function(fix = FALSE) {
#' @export
format_js <- function(fix = TRUE) {
if (fix) {
npm("run", "format-js", "--", "--write")
npm_run("format-js", "--", "--write")
} else {
npm("run", "format-js", "--", "--check")
npm_run("format-js", "--", "--check")
}
}

Expand Down Expand Up @@ -356,9 +411,9 @@ build_sass <- function(watch = FALSE) {

build_sass_node <- function(watch = FALSE) {
if (watch) {
npm("run", "build-sass", "--", "--watch", status_ok = 2)
npm_run("build-sass", "--", "--watch")
} else {
npm("run", "build-sass")
npm_run("build-sass")
}
}

Expand Down Expand Up @@ -389,9 +444,9 @@ build_sass_r <- function() {
#' @export
lint_sass <- function(fix = FALSE) {
if (fix) {
npm("run", "lint-sass", "--", "--fix")
npm_run("lint-sass", "--", "--fix")
} else {
npm("run", "lint-sass")
npm_run("lint-sass")
}
}

Expand All @@ -412,9 +467,9 @@ lint_sass <- function(fix = FALSE) {
#' @export
format_sass <- function(fix = TRUE) {
if (fix) {
npm("run", "format-sass", "--", "--write")
npm_run("format-sass", "--", "--write")
} else {
npm("run", "format-sass", "--", "--check")
npm_run("format-sass", "--", "--check")
}
}

Expand Down Expand Up @@ -445,8 +500,8 @@ format_sass <- function(fix = TRUE) {
#' @export
test_e2e <- function(interactive = FALSE) {
if (interactive) {
npm("run", "test-e2e-interactive")
npm_run("test-e2e-interactive")
} else {
npm("run", "test-e2e")
npm_run("test-e2e")
}
}
Loading
Loading