tools/wasm/prepare-shinylive-app.R

#!/usr/bin/env Rscript

args <- commandArgs(trailingOnly = TRUE)

value_after <- function(flag, default = NULL) {
  hit <- which(args == flag)
  if (!length(hit) || hit[[1]] == length(args)) return(default)
  args[[hit[[1]] + 1L]]
}

app_dir <- value_after("--app-dir", file.path("inst", "shiny"))
out_dir <- value_after("--out-dir", file.path("_site", "app"))
work_dir <- value_after("--work-dir", file.path("_wasm", "app-source"))
library_dir <- value_after("--library-dir", file.path("_wasm", "libraries"))
artifact_ref <- value_after(
  "--artifact-ref",
  Sys.getenv("OPENSPECY_WASM_ARTIFACT_REF", "")
)
package_sha <- value_after("--package-sha", Sys.getenv("GITHUB_SHA", ""))
package_roots_file <- value_after(
  "--package-roots",
  file.path("inst", "shiny", "wasm", "app-package-roots.txt")
)
library_types_file <- value_after(
  "--library-types",
  file.path("inst", "shiny", "wasm", "library-types.txt")
)
manifest_out <- value_after("--manifest-out",
                            file.path(work_dir, "wasm-app-manifest.json"))

if (!nzchar(artifact_ref)) {
  stop("A pinned --artifact-ref or OPENSPECY_WASM_ARTIFACT_REF is required.",
       call. = FALSE)
}
if (!nzchar(package_sha)) {
  stop("A pinned --package-sha or GITHUB_SHA is required.", call. = FALSE)
}

read_manifest_lines <- function(path) {
  x <- readLines(path, warn = FALSE)
  x <- sub("#.*$", "", x)
  x <- trimws(x)
  x[nzchar(x)]
}

desc <- read.dcf("DESCRIPTION")[1, ]
package_roots <- read_manifest_lines(package_roots_file)
package_names <- package_roots
package_names[package_names == "local::."] <- desc[["Package"]]
package_names <- unique(package_names)
library_types <- read_manifest_lines(library_types_file)
quote_chr <- function(x) {
  paste0("\"", gsub("\"", "\\\\\"", x, fixed = TRUE), "\"")
}

parent_dir <- dirname(work_dir)
copied_dir <- file.path(parent_dir, basename(app_dir))
if (dir.exists(work_dir)) unlink(work_dir, recursive = TRUE)
if (dir.exists(copied_dir)) unlink(copied_dir, recursive = TRUE)
dir.create(parent_dir, recursive = TRUE, showWarnings = FALSE)
ok <- file.copy(app_dir, parent_dir, recursive = TRUE)
if (!isTRUE(ok)) {
  stop("Unable to copy Shiny app source from ", app_dir, call. = FALSE)
}
if (!identical(normalizePath(copied_dir, winslash = "/", mustWork = TRUE),
               normalizePath(work_dir, winslash = "/", mustWork = FALSE))) {
  if (dir.exists(work_dir)) unlink(work_dir, recursive = TRUE)
  file.rename(copied_dir, work_dir)
}

if (!dir.exists(library_dir)) {
  stop("Staged library directory does not exist: ", library_dir, call. = FALSE)
}
dir.create(file.path(work_dir, "libraries"), recursive = TRUE,
           showWarnings = FALSE)
file.copy(
  list.files(library_dir, full.names = TRUE),
  file.path(work_dir, "libraries"),
  recursive = TRUE,
  overwrite = TRUE
)

writeLines(
  c(
    "# Generated by tools/wasm/prepare-shinylive-app.R.",
    "options(openspecy.shiny.wasm = TRUE)",
    sprintf("options(openspecy.shiny.wasm.artifact = %s)",
            quote_chr(artifact_ref)),
    sprintf("options(openspecy.shiny.wasm.package_version = %s)",
            quote_chr(unname(desc[["Version"]]))),
    sprintf("options(openspecy.shiny.wasm.package_sha = %s)",
            quote_chr(package_sha)),
    sprintf("options(openspecy.shiny.wasm.packages = c(%s))",
            paste(quote_chr(package_names), collapse = ", ")),
    sprintf("options(openspecy.shiny.wasm.libraries = c(%s))",
    paste(quote_chr(library_types), collapse = ", ")),
    "Sys.setenv(OPENSPECY_SHINY_WASM = \"true\")",
    "Sys.setenv(OPENSPECY_SHINY_LIBRARY_PATH = \"libraries\")"
  ),
  file.path(work_dir, "wasm-config.R")
)

status <- system2(
  file.path(R.home("bin"), "Rscript"),
  c("tools/wasm/write-app-manifest.R",
    "--artifact-ref", artifact_ref,
    "--package-sha", package_sha,
    "--out", manifest_out,
    "--package-roots", package_roots_file,
    "--library-types", library_types_file)
)
if (!identical(status, 0L)) {
  stop("Failed to write Shinylive wasm app manifest.", call. = FALSE)
}
manifest_copy <- file.path(work_dir, "wasm-app-manifest.json")
if (!identical(normalizePath(manifest_out, winslash = "/", mustWork = FALSE),
               normalizePath(manifest_copy, winslash = "/",
                             mustWork = FALSE))) {
  file.copy(manifest_out, manifest_copy, overwrite = TRUE)
}

if (dir.exists(out_dir)) unlink(out_dir, recursive = TRUE)
shinylive::export(
  work_dir,
  out_dir,
  quiet = FALSE,
  # The action-built pinned library image is added after export. Letting
  # Shinylive resolve packages here would use its floating default wasm repo.
  wasm_packages = FALSE,
  max_filesize = "500M",
  template_params = list(title = "OpenSpecy")
)

source(file.path("tools", "wasm", "patch-shinylive-workerfs.R"))
patch_shinylive_workerfs(out_dir)

cat("Prepared Shinylive app at: ",
    normalizePath(out_dir, winslash = "/", mustWork = FALSE), "\n", sep = "")

Try the OpenSpecy package in your browser

Any scripts or data that you put into this service are public.

OpenSpecy documentation built on Oct. 6, 2026, 1:07 a.m.