Skip to content

Commit fbf2850

Browse files
authored
Move shinylive from app type to runtime strategy (#10)
* Rewrite vignettes for the shinylive-as-strategy API * Make shinylive a runtime strategy and autodetect app_type Collapses app_type to the language (r-shiny, py-shiny) and moves shinylive from the app-type axis to runtime_strategy alongside bundled, system, auto-download, and container. All five strategies are legal with both languages. - detect_app_type() autodetects from app.R, ui.R + server.R, or app.py; export() / app_check() treat app_type = NULL as autodetect. - runtime_strategy defaults to "shinylive" when unset. - Legacy r-shinylive / py-shinylive values still accepted with a cli::cli_warn of class shinyelectron_deprecated_app_type and translated to the canonical language plus "shinylive" strategy. Pairing a legacy type with a non-shinylive strategy errors. - Multi-app suites honor per-app runtime_strategy in the YAML; the apps manifest carries the resolved per-app strategy and the Electron shell's backend router reads it before falling back. - Config schema: build.type may be omitted (autodetect); validator accepts "shinylive"; init_config() writes a template with type commented out. - Drops validate_runtime_strategy_for_app_type and the SHINYLIVE / NATIVE / ALL_APP_TYPES constants. R_TYPES / PY_TYPES collapse to single-element vectors. Tests cover detection rules, the shim's warning class, the conflict error, legacy -> canonical mapping, mixed-strategy multi-app suites, and snapshots of the detector's three abort messages. NEWS.md folds the change into the 0.2.0 entry. * Add diagrams for runtime strategies, signing, container, config Adds twelve new SVGs and refreshes the runtime-decision tree to cover the strategy comparison, the per-strategy ships/launch/running diagrams, config overview, container anatomy and image sources, the launch flow, and the two signing diagrams. * Polish strategy and configuration vignettes for shinylive-as-strategy * Wire splash and preloader options through to the lifecycle window splash.enabled, splash.duration, splash.background, preloader.background, and preloader.style are now consumed by lifecycle.html. The splash state is gated by splash.enabled (false opens directly to the preloader), the splash.duration value caps a setTimeout that delays the next state until the minimum elapses, and the body background swaps to preloader.background on first transition. preloader.style toggles spinner / bar / dots indicators above the message via mustache sections. Drops splash.width, splash.height, preloader.enabled, and lifecycle.splash_min_duration from the schema (they were validated but never read), tightens menu.template's accepted values to default and minimal, and updates init_config()'s YAML template to match. Adds tests covering defaults, fallback behaviour for the new background overrides, mutually-exclusive style flags, and validator behaviour. * Render auto-updater booleans as valid JavaScript Whisker prints R booleans as TRUE / FALSE (uppercase), which is invalid JavaScript. Replace direct {{auto_download}} / {{auto_install}} interpolation with mustache section equivalents that emit lowercase true / false. * Rename Advanced Features vignette to Customizations and add diagrams Reframes the vignette around the three knobs that wrap the Shiny content in a desktop build: the lifecycle window, the system tray, and the application menu. Six new annotated SVGs (lifecycle phases, splash anatomy, tray anatomy, tray button matrix, menu templates, preloader styles) replace what used to be prose-only descriptions. Updates the navbar entry, articles list, and cross-link from the Auto Updates vignette to point at the new file. * Pass over Security Considerations vignette Reframes the intro around the three places desktop Shiny apps can break (shell, app code, release path), reads the webPreferences block live from inst/electron/shared/main.js so it stays in sync, fixes container claims (default images run as root, /app is always mounted), and adds labelled R / Python examples for parameterised shell commands. Trims the security-trust-boundary diagram so the arrow heads sit at the edges of the destination boxes instead of overshooting into them. * Pass over Node.js Management vignette Reframes the intro around why R / Python users want a local install, fixes the cache path (lives under rappdirs::user_cache_dir, not ~/.shinyelectron), corrects the resolution rule (highest semver wins regardless of install order), and adds a sentence about the Node.js 22.0.0 minimum required by Electron 41. * Pass over Troubleshooting vignette and add phase map diagram Reframes the intro around the three failure phases (install, build, runtime), moves "Reading sitrep results in code" up to sit with the diagnostic functions, drops redundant "Covers:" bullet lists that duplicated the function table, and renames "Migrating from the old API" to "Deprecation warnings" to match how readers actually arrive there. Adds a three-column SVG that maps each phase to its sitrep function and the top errors users hit there, so readers can scan once and jump to the relevant section. Fixes the Slow Builds (Windows) instruction to point at the real cache path via cache_dir() instead of the non-existent ~/.shinyelectron. * Refactor GitHub Actions vignette around the bundled template The vignette now reads the env-vars block live from inst/templates/github-actions-build.yml via a small helper, so the documented values cannot drift from what shinyelectron ships. The long hand-rolled "Build it from scratch" workflow is replaced with a pointer to the template (system.file plus the GitHub URL) and a short tour of what the template builds. The aspirational coatless-actions/shiny-to-electron quickstart is moved to a "Coming soon" section since the action does not have a published release yet; the URL is also corrected (it was pointing at the singular coatless-action org, which 404s). Bumps GitHub Actions version pins to match the latest stable releases: actions/checkout@v6, actions/cache@v5, actions/download-artifact@v8, softprops/action-gh-release@v3. * Update NEWS.md for 0.2.0 customization wiring and doc pass * Fix R CMD check failures on the runtime-strategy branch The dist_has_platform_artifact roxygen block had absorbed an unattached "Build for target platforms" docblock for the next function down, so \usage listed only output_dir and p while \arguments documented platform / arch / verbose. Splits the two roxygen blocks so each function has its own and regenerates the .Rd files. The "export defaults runtime_strategy to shinylive when NULL" test mocked build_electron_app but not convert_app_to_shinylive, so the shinylive conversion still ran and pulled on pkgcache, which complains that R_USER_CACHE_DIR is unset under R CMD check. Adds a matching mockery::stub for convert_app_to_shinylive; the test now exercises only the strategy-resolution path it was meant to cover.
1 parent d07b3d4 commit fbf2850

90 files changed

Lines changed: 5747 additions & 2782 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

NEWS.md

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,50 @@
11
# shinyelectron 0.2.0
22

3-
## New app types
3+
## App types and autodetection
44

5-
* Added Python Shiny support: `py-shinylive` (browser-based via Pyodide) and
6-
`py-shiny` (native with Python runtime). All four app types are now fully
7-
implemented.
5+
* Python Shiny is now supported alongside R Shiny. `app_type` takes
6+
`"r-shiny"` or `"py-shiny"`, with `NULL` (default) meaning
7+
autodetect from the files in `appdir`. `detect_app_type()` resolves
8+
`app.R`, `ui.R` + `server.R`, or `app.py`; ambiguous layouts abort
9+
with a pointer to the multi-app-suites workflow.
10+
* `export(appdir, destdir)` with no other arguments now works for the
11+
common case.
812

913
## Runtime strategies
1014

11-
* Added four runtime strategies for native apps (`r-shiny`, `py-shiny`):
12-
**system**, **bundled**, **auto-download** (default), and **container**.
13-
* Bundled strategy embeds a portable R or Python runtime inside the Electron
15+
* Five strategies, all legal with both languages: **shinylive** (the
16+
default, compiles to WebAssembly and runs in-browser), **bundled**,
17+
**system**, **auto-download**, and **container**.
18+
* Bundled embeds a portable R or Python runtime inside the Electron
1419
app for zero-dependency distribution.
15-
* Auto-download strategy downloads the runtime on first launch and caches it
20+
* Auto-download fetches the runtime on first launch and caches it
1621
locally.
17-
* Container strategy runs apps inside Docker or Podman for full environment
22+
* Container runs apps inside Docker or Podman for full environment
1823
isolation.
24+
* The older `app_type = "r-shinylive"` and `"py-shinylive"` values
25+
are still accepted with a deprecation warning of class
26+
`shinyelectron_deprecated_app_type`; they translate to the canonical
27+
language plus `runtime_strategy = "shinylive"`. Pairing a legacy
28+
type with a non-shinylive strategy is an error. The shim will be
29+
removed in a future release.
1930

2031
## Multi-app suites
2132

22-
* Bundle multiple Shiny apps into a single Electron shell with a launcher UI.
23-
* Configure via `_shinyelectron.yml` with an `apps` array.
24-
* Python suites read dependencies from a single suite-root `requirements.txt`.
33+
* Bundle multiple Shiny apps into a single Electron shell with a
34+
launcher UI.
35+
* Configure via `_shinyelectron.yml` with an `apps` array; each app
36+
entry may carry its own `runtime_strategy`, so a suite can mix
37+
(for example) one shinylive app with a bundled R app.
38+
* Python suites read dependencies from a single suite-root
39+
`requirements.txt`.
40+
41+
## Config file
42+
43+
* `build.type` may be omitted; autodetection runs at build time.
44+
* `build.runtime_strategy: "shinylive"` is accepted alongside the
45+
other four strategies.
46+
* `init_config()` writes a template that leaves `build.type`
47+
commented out and lists all five strategies.
2548

2649
## Lifecycle UI
2750

@@ -31,6 +54,18 @@
3154
screen.
3255
* Port allocation uses OS-assigned ports via `findAvailablePort()` to prevent
3356
collisions when multiple apps run simultaneously.
57+
* Splash and preloader gain working knobs in `_shinyelectron.yml`:
58+
`splash.enabled` toggles the splash state, `splash.duration` sets a
59+
minimum display time before transitioning, `splash.background` and
60+
`preloader.background` override the lifecycle window background, and
61+
`preloader.style` picks the loading indicator (`spinner`, `bar`, or
62+
`dots`).
63+
64+
## Bug fixes
65+
66+
* `auto_download` and `auto_install` were rendered as R-style `TRUE` /
67+
`FALSE` in the generated `main.js`, which is invalid JavaScript. Booleans
68+
now render through mustache sections so the auto-updater wiring is valid.
3469

3570
## Code signing
3671

@@ -66,15 +101,18 @@
66101

67102
* New vignettes: Runtime Strategies, Multi-App Suites, Code Signing, Container
68103
Strategy, Security Considerations.
104+
* Renamed the Advanced Features vignette to Customizations and rewrote it
105+
around the splash, tray, and menu options with annotated diagrams.
69106
* Updated Getting Started, Configuration, and Troubleshooting vignettes for
70107
Python support, all runtime strategies, and Windows-specific guidance.
108+
* The GitHub Actions vignette now reads its env-vars block live from
109+
`inst/templates/github-actions-build.yml`, so the documented values stay in
110+
sync with the shipped template.
71111

72112
## Internal
73113

74114
* Extracted `run_command_safe()` helper for repeated `processx::run()` patterns.
75115
* Extracted `killProcessTree()` to shared JS utils for process cleanup.
76-
* Added app type group constants (`SHINYLIVE_TYPES`, `NATIVE_TYPES`,
77-
`R_TYPES`, `PY_TYPES`) to reduce stringly-typed code.
78116
* Cached `available.packages()` during bundled R builds to avoid redundant
79117
CRAN network calls.
80118
* Fixed event listener leak in multi-app mode (`removeAllListeners()` instead

R/app_check.R

Lines changed: 53 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
#'
77
#' @param appdir Character string. Path to the app directory. Default ".".
88
#' @param app_type Character string or NULL. App type override.
9-
#' If NULL, reads from config or defaults to "r-shinylive".
9+
#' If NULL, reads from config or autodetects from files in `appdir`.
1010
#' @param runtime_strategy Character string or NULL. Runtime strategy override.
1111
#' @param platform Character vector or NULL. Target platforms override.
1212
#' @param sign Logical or NULL. Signing override.
@@ -69,23 +69,42 @@ app_check <- function(appdir = ".", app_type = NULL, runtime_strategy = NULL,
6969
list()
7070
})
7171

72-
# Resolve parameters from config
73-
app_type <- app_type %||% config$build$type %||% "r-shinylive"
74-
runtime_strategy <- infer_runtime_strategy(runtime_strategy %||% config$build$runtime_strategy, app_type)
72+
# Resolve parameters. Order: function arg > config > autodetect (type)
73+
# or default (strategy).
74+
normalized <- normalize_app_type_arg(app_type, runtime_strategy)
75+
app_type <- normalized$app_type
76+
runtime_strategy <- normalized$runtime_strategy
77+
78+
if (is.null(app_type)) {
79+
cfg_type <- config$build$type
80+
if (!is.null(cfg_type) && nzchar(cfg_type)) {
81+
cfg_normalized <- normalize_app_type_arg(cfg_type, runtime_strategy)
82+
app_type <- cfg_normalized$app_type
83+
runtime_strategy <- runtime_strategy %||% cfg_normalized$runtime_strategy
84+
}
85+
}
86+
if (is.null(app_type)) {
87+
app_type <- tryCatch(detect_app_type(appdir), error = function(e) NULL)
88+
if (is.null(app_type)) {
89+
errors <- c(errors, "Could not determine app type (no app.R/app.py/server.R+ui.R found)")
90+
if (verbose) cli::cli_alert_danger("App type: could not autodetect")
91+
return(invisible(list(pass = FALSE, errors = errors, warnings = warnings, info = info)))
92+
}
93+
}
94+
runtime_strategy <- runtime_strategy %||% config$build$runtime_strategy %||% "shinylive"
95+
7596
platform <- platform %||% config$build$platforms %||% detect_current_platform()
7697
sign <- sign %||% isTRUE(config$signing$sign)
7798

7899
if (verbose) {
79100
cli::cli_alert_info("Type: {.val {app_type}}")
80-
if (app_type %in% NATIVE_TYPES) {
81-
cli::cli_alert_info("Runtime strategy: {.val {runtime_strategy}}")
82-
}
101+
cli::cli_alert_info("Runtime strategy: {.val {runtime_strategy}}")
83102
cli::cli_alert_info("Platform(s): {.val {platform}}")
84103
}
85104

86105
# --- Check: App structure ---
87106
tryCatch({
88-
if (app_type %in% R_TYPES) {
107+
if (app_type == "r-shiny") {
89108
validate_shiny_app_structure(appdir)
90109
if (verbose) cli::cli_alert_success("App structure: {.file app.R} found")
91110
} else {
@@ -114,7 +133,7 @@ app_check <- function(appdir = ".", app_type = NULL, runtime_strategy = NULL,
114133

115134
# --- Check: Runtime ---
116135
if (runtime_strategy == "system") {
117-
if (app_type %in% R_TYPES) {
136+
if (app_type == "r-shiny") {
118137
tryCatch({
119138
rscript_path <- validate_r_available()
120139
if (verbose) cli::cli_alert_success("R: available at {.path {rscript_path}}")
@@ -123,7 +142,7 @@ app_check <- function(appdir = ".", app_type = NULL, runtime_strategy = NULL,
123142
if (verbose) cli::cli_alert_danger("R: {e$message}")
124143
})
125144
}
126-
if (app_type %in% PY_TYPES) {
145+
if (app_type == "py-shiny") {
127146
tryCatch({
128147
validate_python_available()
129148
if (verbose) cli::cli_alert_success("Python: available")
@@ -153,44 +172,43 @@ app_check <- function(appdir = ".", app_type = NULL, runtime_strategy = NULL,
153172
})
154173
}
155174

156-
# --- Check: R shinylive package (for r-shinylive) ---
157-
if (app_type == "r-shinylive") {
158-
if (requireNamespace("shinylive", quietly = TRUE)) {
159-
if (verbose) cli::cli_alert_success("shinylive R package: installed")
160-
} else {
161-
errors <- c(errors, "shinylive R package not installed")
162-
if (verbose) cli::cli_alert_danger("shinylive R package: not installed")
175+
# --- Check: shinylive tooling (only when strategy is shinylive) ---
176+
if (runtime_strategy == "shinylive") {
177+
if (app_type == "r-shiny") {
178+
if (requireNamespace("shinylive", quietly = TRUE)) {
179+
if (verbose) cli::cli_alert_success("shinylive R package: installed")
180+
} else {
181+
errors <- c(errors, "shinylive R package not installed")
182+
if (verbose) cli::cli_alert_danger("shinylive R package: not installed")
183+
}
184+
} else if (app_type == "py-shiny") {
185+
tryCatch({
186+
validate_python_available()
187+
validate_python_shinylive_installed()
188+
if (verbose) cli::cli_alert_success("Python shinylive: installed")
189+
}, error = function(e) {
190+
errors <<- c(errors, e$message)
191+
if (verbose) cli::cli_alert_danger("Python shinylive: {e$message}")
192+
})
163193
}
164194
}
165195

166-
# --- Check: Python shinylive (for py-shinylive) ---
167-
if (app_type == "py-shinylive") {
168-
tryCatch({
169-
validate_python_available()
170-
validate_python_shinylive_installed()
171-
if (verbose) cli::cli_alert_success("Python shinylive: installed")
172-
}, error = function(e) {
173-
errors <<- c(errors, e$message)
174-
if (verbose) cli::cli_alert_danger("Python shinylive: {e$message}")
175-
})
176-
}
177-
178196
# --- Check: Dependencies ---
179197
tryCatch({
180-
dep_result <- resolve_app_dependencies(appdir, app_type, config)
198+
dep_result <- resolve_app_dependencies(appdir, app_type, runtime_strategy, config)
181199
if (!is.null(dep_result) && length(dep_result$packages) > 0) {
182200
dep_msg <- paste(dep_result$packages, collapse = ", ")
183201
info <- c(info, paste0("Dependencies (", dep_result$language, "): ", dep_msg))
184202
if (verbose) cli::cli_alert_success("Dependencies: {dep_msg}")
185-
} else if (app_type %in% SHINYLIVE_TYPES) {
186-
# For shinylive types, resolve_app_dependencies returns NULL because
187-
# shinylive handles its own deps. Still scan for informational purposes.
203+
} else if (runtime_strategy == "shinylive") {
204+
# shinylive handles its own deps, so resolve_app_dependencies returns NULL.
205+
# Still scan for informational purposes.
188206
detected <- tryCatch({
189-
if (grepl("^r-", app_type)) detect_r_dependencies(appdir)
207+
if (app_type == "r-shiny") detect_r_dependencies(appdir)
190208
else detect_py_dependencies(appdir)
191209
}, error = function(e) character(0))
192210
if (length(detected) > 0) {
193-
lang <- if (grepl("^r-", app_type)) "R" else "Python"
211+
lang <- if (app_type == "r-shiny") "R" else "Python"
194212
dep_msg <- paste(detected, collapse = ", ")
195213
info <- c(info, paste0("Dependencies (", lang, "): ", dep_msg))
196214
if (verbose) cli::cli_alert_success("Dependencies: {dep_msg}")

R/build-project.R

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,35 @@ setup_electron_project <- function(output_dir, app_name, app_type, verbose = TRU
2525
#' @param app_dir Character source app directory
2626
#' @param output_dir Character destination directory
2727
#' @param app_type Character application type
28+
#' @param runtime_strategy Character resolved runtime strategy. When
29+
#' `"shinylive"` the source is already a WebAssembly bundle, so the
30+
#' Shiny entrypoint sanity check is skipped.
2831
#' @param verbose Logical whether to show progress
2932
#' @keywords internal
30-
copy_app_files <- function(app_dir, output_dir, app_type, verbose = TRUE) {
33+
copy_app_files <- function(app_dir, output_dir, app_type,
34+
runtime_strategy = NULL, verbose = TRUE) {
3135
if (verbose) {
3236
cli::cli_alert_info("Copying application files...")
3337
}
3438

3539
dest_app_dir <- fs::path(output_dir, "src", "app")
3640
copy_dir_contents(app_dir, dest_app_dir)
3741

42+
# For shinylive the source directory holds index.html and WASM assets,
43+
# not app.R / app.py, so skip the native-entrypoint sanity check.
44+
if (!is.null(runtime_strategy) && runtime_strategy == "shinylive") {
45+
if (verbose) {
46+
cli::cli_alert_success("Copied application files")
47+
}
48+
return(invisible(NULL))
49+
}
50+
3851
# Sanity check: for native Shiny apps, confirm the entrypoint made it across.
3952
# Catches copy-layout bugs at build time rather than at runtime inside Electron.
4053
entry_files <- switch(app_type,
4154
"r-shiny" = c("app.R", "server.R", "ui.R"),
4255
"py-shiny" = "app.py",
43-
NULL # shinylive types validate their own output
56+
NULL
4457
)
4558
if (!is.null(entry_files)) {
4659
found <- any(fs::file_exists(fs::path(dest_app_dir, entry_files)))

R/build-steps.R

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,20 +41,17 @@ install_npm_dependencies <- function(output_dir, verbose = TRUE) {
4141
}
4242

4343

44-
#' Build for target platforms
45-
#'
46-
#' @param output_dir Character Electron project directory
47-
#' @param platform Character vector of target platforms
48-
#' @param arch Character vector of target architectures
49-
#' @param verbose Logical whether to show progress
50-
#' @keywords internal
51-
#' Check whether electron-builder produced output for a platform.
44+
#' Check whether electron-builder produced output for a platform
5245
#'
5346
#' electron-builder 26.x has a known bug where the build completes and the
5447
#' installer is written to disk, but the process then exits with status 1
5548
#' during post-build publish metadata. When that happens, processx-invoked
5649
#' npm inherits the non-zero exit even though the .dmg/.exe/.AppImage is
5750
#' sitting right there. We treat "artifact exists" as success.
51+
#'
52+
#' @param output_dir Character Electron project directory
53+
#' @param p Character platform identifier (`"mac"`, `"win"`, or `"linux"`)
54+
#' @return Logical: `TRUE` if a platform-specific installer is present.
5855
#' @keywords internal
5956
dist_has_platform_artifact <- function(output_dir, p) {
6057
dist_dir <- fs::path(output_dir, "dist")
@@ -74,6 +71,14 @@ dist_has_platform_artifact <- function(output_dir, p) {
7471
logical(1)))
7572
}
7673

74+
#' Build for target platforms
75+
#'
76+
#' @param output_dir Character Electron project directory
77+
#' @param platform Character vector of target platforms
78+
#' @param arch Character vector of target architectures
79+
#' @param sign Logical whether to code-sign the build
80+
#' @param verbose Logical whether to show progress
81+
#' @keywords internal
7782
build_for_platforms <- function(output_dir, platform, arch, sign = FALSE, verbose = TRUE) {
7883
if (verbose) {
7984
cli::cli_alert_info("Building for platforms: {paste(platform, collapse = ', ')}")

R/build.R

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,13 @@
66
#' @param app_dir Character string. Path to the converted Shiny/shinylive application.
77
#' @param output_dir Character string. Path where the built Electron app will be saved.
88
#' @param app_name Character string. Name of the application. If NULL, uses the base name of app_dir.
9-
#' @param app_type Character string. Type of application: "r-shinylive", "r-shiny", "py-shinylive", or "py-shiny".
10-
#' @param runtime_strategy Character string. Runtime strategy: "shinylive", "bundled",
11-
#' "system", "auto-download", or "container". Default is "shinylive".
9+
#' @param app_type Character string. Language of the Shiny app: `"r-shiny"` or
10+
#' `"py-shiny"`. The legacy values `"r-shinylive"` / `"py-shinylive"` are
11+
#' accepted with a deprecation warning and translate to the canonical
12+
#' language plus `runtime_strategy = "shinylive"`.
13+
#' @param runtime_strategy Character string. Runtime strategy: `"shinylive"`,
14+
#' `"bundled"`, `"system"`, `"auto-download"`, or `"container"`. Default
15+
#' `"shinylive"`.
1216
#' @param platform Character vector. Target platforms: "win", "mac", "linux". If NULL, builds for current platform.
1317
#' @param arch Character vector. Target architectures: "x64", "arm64". If NULL, uses current architecture.
1418
#' @param icon Character string. Path to application icon file. Platform-specific format required.
@@ -37,28 +41,35 @@
3741
#' app_dir = "path/to/shinylive/app",
3842
#' output_dir = "path/to/electron/build",
3943
#' app_name = "My Shiny App",
40-
#' app_type = "r-shinylive"
44+
#' app_type = "r-shiny"
4145
#' )
4246
#'
4347
#' # Build for multiple platforms
4448
#' build_electron_app(
4549
#' app_dir = "path/to/app",
4650
#' output_dir = "path/to/build",
4751
#' app_name = "My App",
48-
#' app_type = "r-shinylive",
52+
#' app_type = "r-shiny",
4953
#' platform = c("win", "mac", "linux")
5054
#' )
5155
#' }
5256
#'
5357
#' @export
54-
build_electron_app <- function(app_dir, output_dir, app_name = NULL, app_type = "r-shinylive",
58+
build_electron_app <- function(app_dir, output_dir, app_name = NULL, app_type = "r-shiny",
5559
runtime_strategy = "shinylive", sign = FALSE,
5660
platform = NULL, arch = NULL, icon = NULL,
5761
config = NULL, overwrite = FALSE, verbose = TRUE) {
5862

5963
# Validate inputs
6064
validate_directory_exists(app_dir, "Application directory")
65+
66+
# Normalize legacy app_type values
67+
normalized <- normalize_app_type_arg(app_type, runtime_strategy)
68+
app_type <- normalized$app_type %||% app_type
69+
runtime_strategy <- normalized$runtime_strategy %||% runtime_strategy
70+
6171
validate_app_type(app_type)
72+
validate_runtime_strategy(runtime_strategy)
6273

6374
if (is.null(app_name)) {
6475
app_name <- basename(app_dir)
@@ -119,7 +130,8 @@ build_electron_app <- function(app_dir, output_dir, app_name = NULL, app_type =
119130

120131
# Step 2: Copy application files
121132
if (verbose) cli::cli_progress_update(id = pb, set = 2)
122-
copy_app_files(app_dir, output_dir, app_type, verbose = verbose)
133+
copy_app_files(app_dir, output_dir, app_type,
134+
runtime_strategy = runtime_strategy, verbose = verbose)
123135

124136
# Step 2.5: Embed R runtime for bundled strategy
125137
if (runtime_strategy == "bundled" && grepl("^r-", app_type)) {

0 commit comments

Comments
 (0)