diff --git a/DESCRIPTION b/DESCRIPTION index d735755..200318d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -2,7 +2,7 @@ Package: RInno Type: Package OS_type: windows Title: An Installation Framework for Shiny Apps -Version: 1.0.0.9000 +Version: 1.0.0 Authors@R: c( person("Jon", "Hill", email = "jon.mark.hill@gmail.com", role = c("aut", "cre", "cph")), person("W. Lee", "Pang", role = c("aut", "cph"), comment = "DesktopDeployR project at https://github.com/wleepang/DesktopDeployR"), diff --git a/NAMESPACE b/NAMESPACE index 83ba983..d5cc522 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -16,7 +16,6 @@ export(add_pkgs) export(code_section) export(compile_iss) export(copy_installation) -export(cran_version) export(create_app) export(create_bat) export(create_config) diff --git a/NEWS.md b/NEWS.md index ca666e3..2323d36 100644 --- a/NEWS.md +++ b/NEWS.md @@ -45,7 +45,7 @@ * Thanks Dean Attali ([@daattali](https://github.com/daattali/)) for this feature request * Fixed `error_log` so that its name can be customized properly * Cleaned up installation paths -* Added Firefox to the browser search path in `app.R`: +* Added Firefox to the browser search path in `launch_app.R`: 1. Chrome 2. Firefox 3. Internet Explorer diff --git a/R/copy_installation.R b/R/copy_installation.R index 23d2d01..71a915b 100644 --- a/R/copy_installation.R +++ b/R/copy_installation.R @@ -3,7 +3,7 @@ #' This function moves files stored in \code{system.file('installation', package = 'RInno')} to \code{app_dir}: #' \itemize{ #' \item Icons for installer and app, \emph{setup.ico}, \emph{default.ico} and \emph{default.png}. -#' \item Files that manage app start up, \emph{utils/package_manager.R} and \emph{utils/app.R}. +#' \item Files that manage app start up, \emph{utils/package_manager.R} and \emph{utils/launch_app.R}. #' \item First/last page of the installation wizard, \emph{infobefore.txt} and \emph{infoafter.txt}. #' \item Batch support files, \emph{utils/wsf/run.wsf}, \emph{utils/wsf/js/run.js}, \emph{utils/wsf/js/json2.js}, and \emph{utils/wsf/js/JSON.minify.js}. #' } diff --git a/R/create_app.R b/R/create_app.R index 4767c8e..55d452a 100644 --- a/R/create_app.R +++ b/R/create_app.R @@ -5,7 +5,7 @@ #' Creates the following files in \code{app_dir}: #' \itemize{ #' \item Icons for installer and app, \emph{setup.ico} and \emph{default.ico} respectively. -#' \item Files that manage app start up, \emph{utils/package_manager.R}, \emph{utils/ensure.R}, and \emph{utils/app.R}. +#' \item Files that manage app start up, \emph{utils/package_manager.R}, \emph{utils/ensure.R}, and \emph{utils/launch_app.R}. #' \item First/last page of the installer, \emph{infobefore.txt} and \emph{infoafter.txt}. #' \item Batch support files, \emph{utils/wsf/run.wsf}, \emph{utils/wsf/js/run.js}, \emph{utils/wsf/js/json2.js}, \emph{utils/wsf/js/JSON.minify.js}. #' \item A configuration file, \emph{config.cfg}. See \code{\link{create_config}} for details. @@ -19,6 +19,7 @@ #' @param pkgs Character vector of package dependencies. Remote development versions are supported via \code{remotes}. \code{pkgs} are downloaded into \code{file.path(app_dir, pkgs_path)} as Windows binary packages (.zip). If you build binary packages and store them there before calling \code{create_app}, they will be included as well. #' @param pkgs_path Default location inside the app working directory to install package dependencies This defaults to \code{pkgs_path = "bin"} #' @param remotes Character vector of GitHub repository addresses in the format \code{username/repo[/subdir][\@ref|#pull]} for GitHub package dependencies. +#' @param locals Character vector of local package dependencies. Deprecated as of v1.0.0. Use \code{pkgs} instead. #' @param include_R To include R in the installer, \code{include_R = TRUE}. The version of R specified by \code{R_version} is used. The installer will check each user's registry and only install R if necessary. #' @param R_version R version to use. Supports inequalities. Defaults to: \code{paste0(">=", R.version$major, '.', R.version$minor)}. #' @param include_Pandoc To include Pandoc in the installer, \code{include_Pandoc = TRUE}. If installing a flexdashboard app, some users may need a copy of Pandoc. The installer will check the user's registry for the version of Pandoc specified in \code{Pandoc_version} and only install it if necessary. @@ -58,6 +59,7 @@ create_app <- function( pkgs_path = "bin", repo = "https://cran.rstudio.com", remotes = "none", + locals = NULL, app_repo_url = "none", auth_user = "none", auth_pw = "none", @@ -78,11 +80,26 @@ create_app <- function( # To capture arguments for other function calls dots <- list(...) + # 1.0.0 deprecation messages + if (!is.null(locals)) { + warning("locals is deprecated. Please use pkgs instead.", call. = FALSE) + pkgs <- pkgs %>% standardize_pkgs %>% add_pkgs(locals) + } + if (user_browser != "electron") { + warning(glue::glue("user_browser = {glue::double_quote(user_browser)} will be deprecated in the next release. Please use user_browser = \"electron\" in the future."), call. = FALSE) + } + if (include_Chrome) { + warning("include_Chrome will be deprecated in the next release. Please use user_browser = \"electron\"", call. = FALSE) + } + if (!is.null(dots$ping_site)) { + warning("ping_site is deprecated in favor of self-contained dependency management in the .exe.", call. = FALSE) + } + # If app_name is not a character, exit - if (class(app_name) != "character") stop("app_name must be a character.", call. = F) + if (class(app_name) != "character") stop("app_name must be a character.", call. = FALSE) # If dir_out is not a character, exit - if (class(dir_out) != "character") stop("dir_out must be a character.", call. = F) + if (class(dir_out) != "character") stop("dir_out must be a character.", call. = FALSE) # If not TRUE/FALSE, exit logicals <- c( @@ -132,7 +149,7 @@ create_app <- function( repo = repo, error_log = dots$error_log, app_repo_url = app_repo_url, auth_user = auth_user, auth_pw = auth_pw, auth_token = auth_token, - user_browser = user_browser, ping_site = dots$ping_site) + user_browser = user_browser) # Build the iss script start_iss(app_name) %>% diff --git a/R/create_config.R b/R/create_config.R index 8571265..ebabfec 100644 --- a/R/create_config.R +++ b/R/create_config.R @@ -8,7 +8,6 @@ #' @param auth_pw Bitbucket password matching the above username. #' @param auth_token To install from a private Github repo, generate a personal access token (PAT) in \url{https://github.com/settings/tokens} and supply to this argument. This is safer than using a password because you can easily delete a PAT without affecting any others. #' @param user_browser Character for the default browser. Options include "chrome", "firefox", and "ie." -#' @param ping_site URL of a site to ping to check internet connectivity. Defaults to "www.ficonsulting.com". #' #' @author Jonathan M. Hill #' @@ -22,7 +21,7 @@ create_config <- function(app_name, app_dir = getwd(), remotes = "none", repo = "https://cran.rstudio.com", error_log = "error.log", app_repo_url = "none", auth_user = "none", - auth_pw = "none", auth_token = "none", user_browser = "electron", ping_site = "www.ficonsulting.com") { + auth_pw = "none", auth_token = "none", user_browser = "electron") { # Reset defaults if empty for (formal in names(formals(create_config))) { @@ -37,7 +36,8 @@ create_config <- function(app_name, app_dir = getwd(), if (!dir.exists(file.path(app_dir, "utils"))) dir.create(file.path(app_dir, "utils")) # Make sure initial packages & shiny are included - pkgs <- add_pkgs(pkgs, c("jsonlite", "shiny")) + pkgs <- pkgs %>% standardize_pkgs() + pkgs <- pkgs %>% add_pkgs(c("jsonlite", "shiny")) if (app_repo_url != "none") { # Fail early @@ -67,7 +67,7 @@ create_config <- function(app_name, app_dir = getwd(), if (length(flex_file) > 0) { # Make sure flexdashboard and rmarkdown are included in the dependency list - pkgs <- add_pkgs(pkgs, c("flexdashboard", "rmarkdown")) + pkgs <- pkgs %>% add_pkgs(c("flexdashboard", "rmarkdown")) cat("This flexdashboard will be used:\n - ", flex_file, "\n") } else { flex_file <- "none" @@ -83,7 +83,7 @@ create_config <- function(app_name, app_dir = getwd(), list( appname = app_name, pkgs = list( - pkgs_names = standardize_pkgs(pkgs, string = TRUE), + pkgs_names = standardize_pkgs(pkgs), pkgs_loc = file.path(pkgs_path, list.files(file.path(app_dir, pkgs_path))) ), logging = error_log, @@ -94,7 +94,6 @@ create_config <- function(app_name, app_dir = getwd(), auth_token = auth_token, user_browser = tolower(user_browser), nativefier = file.path("nativefier-app", list.files(file.path(app_dir, "nativefier-app"), pattern = ".exe", recursive = TRUE)), - flex_file = flex_file, - ping_site = ping_site), + flex_file = flex_file), file.path(app_dir, "utils/config.cfg"), pretty = T, auto_unbox = T) } diff --git a/R/download_packages.R b/R/download_packages.R index 1858773..ce64f86 100644 --- a/R/download_packages.R +++ b/R/download_packages.R @@ -17,7 +17,7 @@ download_packages <- function(app_dir, pkgs_path, pkgs, repo, remotes, auth_user if (any(lapply(pkgs, class) != "character")) stop("`pkgs` must be a character vector.", call. = FALSE) # Standardize pkgs - standard_deps <- standardize_pkgs(pkgs, check_version = TRUE, string = TRUE) + standard_deps <- standardize_pkgs(pkgs) # Find all the pkg dependencies pkg_deps <- tools::package_dependencies(packages = standard_deps, recursive = TRUE) %>% diff --git a/R/utils.R b/R/utils.R index 2313056..654cd7b 100644 --- a/R/utils.R +++ b/R/utils.R @@ -3,7 +3,7 @@ #' @importFrom magrittr %>% #' @param lhs A value or the magrittr placeholder. #' @param rhs A function call using the magrittr semantics. -#' @seealso \code{\link[magrittr]{\%>\%}} +#' @seealso \code{\link[magrittr]{magrittr}} #' @export `%>%` <- magrittr::`%>%` @@ -12,95 +12,37 @@ #' Standardizes (named or not) character vectors of package dependencies and formats it for config.cfg. #' #' @param pkgs Processes \code{pkgs}, and \code{pkgs}, arguments of \code{\link{create_config}} and \code{\link{create_app}}. -#' @param check_version Boolean. If true, check to make sure the package version is not ahead of CRAN. #' -#' @return Package dependency list with version numbers and inequalities. Defaults to \code{paste0(">=", packageVersion(pkg))}. +#' @return Package dependency list #' #' @author William Bradley and Jonathan Hill #' @keywords internal #' @export -standardize_pkgs <- function(pkgs, check_version = FALSE, string = FALSE) { +standardize_pkgs <- function(pkgs) { if (pkgs[1] == "none") return("none") - # remove spaces, create a list and vectors to control the process - pkgs <- gsub(" ", "", pkgs) - pkg_list <- as.list(pkgs) - no_version <- names(pkg_list) == "" - no_inequality <- !grepl("[<>=]", pkgs) + # deprecation message + if (!is.null(names(pkgs))) { + warning("Package versions are no longer supported. Please develop your app using the most recent CRAN version.", call. = FALSE) - # No versions are specified - if (length(no_version) == 0) { + version <- names(pkgs) != "" - tryCatch( - pkg_list <- lapply(pkg_list, utils::packageVersion), - - error = function(e) { - stop(e$message, - "\n\nPlease provide versions of `pkgs` if they are not installed in the development environment.", call. = FALSE) - }) - - names(pkg_list) <- pkgs - - # add greater than or equal to - pkg_list <- lapply(pkg_list, function(x) paste0(">=", x)) - - # Some versions are specified - } else if (sum(no_version) > 0) { - - tryCatch( - pkg_list[no_version] <- lapply(pkg_list[no_version], utils::packageVersion), - - error = function(e) { - stop(e$message, "\n\nPlease provide versions of `pkgs` if they are not installed in the development environment.", call. = FALSE) - }) - - names(pkg_list)[no_version] <- pkgs[no_version] - - # add greater than or equal to - pkg_list[no_inequality] <- lapply(pkg_list[no_inequality], function(x) paste0(">=", x)) - - # All versions are specified - } else { - # add greater than or equal to - pkg_list[no_inequality] <- lapply(pkg_list[no_inequality], function(x) paste0(">=", x)) - } - - # convert to character for JSON - pkgs <- lapply(pkg_list, as.character) - - # Make sure the results are valid - installed_pkgs <- data.frame(utils::installed.packages(), row.names = NULL) - - check_pkgs <- function(pkg, pkg_name) { - breakpoint <- attr(regexpr("[<>=]+", pkg), "match.length") - inequality <- substr(pkg, 1, breakpoint) - required_version <- substr(pkg, breakpoint + 1, nchar(pkg)) - - if (nchar(inequality) > 2 | grepl("=[<>]", inequality)) { - stop(glue::glue("{pkg_name}'s inequality ({inequality}) is not a valid logical operator"), call. = F) - } - if (class(try(numeric_version(required_version), silent = TRUE)) == "try-error") { - stop(glue::glue("{required_version} is not a valid `numeric_version` for {pkg_name} "), call. = F) - } - if (!pkg_name %in% installed_pkgs$Package) { - stop(glue::glue("{pkg_name} is not installed. Make sure it is in `installed.pacakges()` and try again."), call. = F) - } - if (check_version) { - pkg_cran_version <- cran_version(pkg_name) - if (is.null(cran_version)) stop("Can't connect to CRAN") - if (numeric_version(required_version) > pkg_cran_version) { - stop(glue::glue("{pkg_name} v{required_version} is ahead of CRAN - v{cran_version(pkg_name)}. Please add it to `remotes` to use {pkg_name}'s development version from Github/Bitbucket or decrease its version to one published on CRAN."), call. = FALSE) - } + if (length(version) != 0) { + pkgs[version] <- names(pkgs[version]) } } - mapply(check_pkgs, pkgs, names(pkgs)) - if (string) { - return(names(pkgs)) - } else { - return(pkgs) + # Make sure pkgs are installed + installed_pkgs <- data.frame(utils::installed.packages(), row.names = NULL, stringsAsFactors = FALSE) + missing_pkgs <- !pkgs %in% installed_pkgs$Package + if (any(missing_pkgs)) { + stop(glue::glue("{pkgs[missing_pkgs]} is not installed. Please install it and try again."), call. = FALSE) } + + names(pkgs) <- NULL + + return(pkgs) } @@ -155,41 +97,6 @@ sanitize_R_version <- function(R_version, clean = FALSE, R_version_min = "3.0.2" } -#' Check CRAN for package version -#' -#' @param pkg_name String. Package name as published on CRAN. -#' @param cran_url String. First part of the cannonical form of a package website on CRAN. -#' -#' @return The package's version as a \code{numeric_version}. -#' -#' @examples -#' cran_version("shiny") -#' @keywords internal -#' @export -cran_version = function(pkg_name, cran_url = "http://cran.r-project.org/package=") { - - # Create URL - cran_pkg_loc = paste0(cran_url, pkg_name) - - # Establish connection - suppressWarnings(conn <- try(url(cran_pkg_loc), silent = TRUE)) - - # If connection, read in webpage - if (all(class(conn) != "try-error") ) { - suppressWarnings(cran_pkg_page <- try(readLines(conn), silent = TRUE)) - close(conn) - } else { - return(NULL) - } - - # Use regex to find version info - version_line = cran_pkg_page[grep("Version:", cran_pkg_page) + 1] - version_line = gsub("<(td|\\/td)>","",version_line) - numeric_version(version_line) - -} - - #' Add package to named vector #' #' Adds (named or not) package dependencies to a named vector of packages. @@ -208,10 +115,7 @@ cran_version = function(pkg_name, cran_url = "http://cran.r-project.org/package= #' @export add_pkgs <- function(pkgs, pkg) { - pkg_strings <- pkg %in% pkgs - pkg_names <- pkg %in% names(pkgs) - - needed_pkgs <- pkg[!(pkg_names | pkg_strings)] + needed_pkgs <- pkg[!pkg %in% pkgs] if (length(needed_pkgs) > 0) { pkgs <- c(pkgs, needed_pkgs) diff --git a/README.Rmd b/README.Rmd index e5305fa..cf9178a 100644 --- a/README.Rmd +++ b/README.Rmd @@ -123,7 +123,7 @@ create_app( `create_app` passes its arguments to most of the other support functions in RInno. You can (and probably should) specify most things there and they will get passed on. Alternatively, you can provide instructions directly to those support functions like this: ``` -# Copy installation scripts (JavaScript, icons, infobefore.txt, package_manager.R, app.R) +# Copy installation scripts (JavaScript, icons, infobefore.txt, package_manager.R, launch_app.R) copy_installation(app_dir = "my/app/path") # If your users need R installed: diff --git a/README.md b/README.md index 98720c4..8f06360 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,7 @@ If you would like to create a custom installer from within R, you can slowly bui `create_app` passes its arguments to most of the other support functions in RInno. You can (and probably should) specify most things there and they will get passed on. Alternatively, you can provide instructions directly to those support functions like this: - # Copy installation scripts (JavaScript, icons, infobefore.txt, package_manager.R, app.R) + # Copy installation scripts (JavaScript, icons, infobefore.txt, package_manager.R, launch_app.R) copy_installation(app_dir = "my/app/path") # If your users need R installed: diff --git a/cran-comments.md b/cran-comments.md index df08e84..ee17229 100644 --- a/cran-comments.md +++ b/cran-comments.md @@ -1,8 +1,8 @@ ## This update -* numerous bug fixes and feature requests +* major release with updates to package dependency management strategy and user interface ## Test environments -* local OS Windows 7 x64, R 3.5.0 +* local OS Windows 7 x64, R 3.5.1 ## R CMD check results There were 0 errors, 0 Warnings and 0 NOTES on Windows OS. There are warnings/notes on Mac/Linux diff --git a/inst/doc/Introduction.Rmd b/inst/doc/Introduction.Rmd index 53bc855..b6929ef 100644 --- a/inst/doc/Introduction.Rmd +++ b/inst/doc/Introduction.Rmd @@ -118,7 +118,7 @@ create_app( `create_app` passes its arguments to most of the other support functions in RInno. You can (and probably should) specify most things there and they will get passed on. Alternatively, you can provide instructions directly to those support functions like this: ``` -# Copy installation scripts (JavaScript, icons, infobefore.txt, package_manager.R, app.R) +# Copy installation scripts (JavaScript, icons, infobefore.txt, package_manager.R, launch_app.R) copy_installation(app_dir = "my/app/path") # If your users need R installed: diff --git a/inst/doc/Introduction.html b/inst/doc/Introduction.html index 4d75359..b50dfd6 100644 --- a/inst/doc/Introduction.html +++ b/inst/doc/Introduction.html @@ -36,7 +36,7 @@

2018-09-20

-

codecov Project Status: Active - The project has reached a stable, usable state and is being actively developed.

+

codecov Project Status: Active - The project has reached a stable, usable state and is being actively developed.

RInno makes it easy to install local shiny apps by providing an interface between R, Inno Setup, an installer for Windows programs (sorry Mac and Linux users), and Electron, a modern desktop framework used by companies like Github, Slack, Microsoft, Facebook and Docker. RInno is designed to be simple to use (two lines of code at a minimum), yet comprehensive.

If a user does not have R installed, the RInno installer can be configured to ask them to install R along with a shiny app, include_R = TRUE. And similar to Dr. Lee Pang’s DesktopDeployR project, RInno provides a framework for managing software dependencies and error logging features. However, RInno also supports GitHub package dependencies, continuous installation (auto-update on start up), and it is easier to manage with create_app, the main RInno function. DesktopDeployR requires many manual adjustments and a deep understanding of the entire framework to use, but RInno can be learned incrementally and changes automatically flow down stream. You don’t need to remember the 100+ places impacted by changing app_dir. RInno only requires a high-level understanding of what you’d like to accomplish.

@@ -119,7 +119,7 @@

Custom Installations

privilege = "high", # Admin only installation default_dir = "pf") # Install app in to Program Files

create_app passes its arguments to most of the other support functions in RInno. You can (and probably should) specify most things there and they will get passed on. Alternatively, you can provide instructions directly to those support functions like this:

-
# Copy installation scripts (JavaScript, icons, infobefore.txt, package_manager.R, app.R)
+
# Copy installation scripts (JavaScript, icons, infobefore.txt, package_manager.R, launch_app.R)
 copy_installation(app_dir = "my/app/path")
 
 # If your users need R installed:
diff --git a/inst/installation/ensure.R b/inst/installation/ensure.R
index 0583542..1500b9a 100644
--- a/inst/installation/ensure.R
+++ b/inst/installation/ensure.R
@@ -4,9 +4,6 @@ installed_pkgs = data.frame(installed.packages(applibpath), stringsAsFactors = F
 ensure <- function(pkgs, lib_loc = applibpath) {
 
   pkg_names <- gsub("_.*", "", basename(pkgs))
-  start <- attr(regexpr(".*_", pkgs), "match.length") + 1
-  stop <- attr(regexpr(".*", pkgs), "match.length") - 4
-  pkg_version <- substr(pkgs, start, stop)
 
   for (i in seq_along(pkgs)) {
     setWinProgressBar(pb,
diff --git a/inst/installation/app.R b/inst/installation/launch_app.R
similarity index 96%
rename from inst/installation/app.R
rename to inst/installation/launch_app.R
index 0a0d418..fb2d2fe 100644
--- a/inst/installation/app.R
+++ b/inst/installation/launch_app.R
@@ -1,97 +1,97 @@
-# app launching code
-config <- jsonlite::fromJSON("utils/config.cfg")
-reg_paths <- jsonlite::fromJSON("utils/regpaths.json")
-
-# This function is used to apply the web browser configuration and registry
-# information on app start up. If a user does not have the user browser,
-# their defult browser will be used.
-start_app <- function(
-  app_name = config$appname,
-  user_browser = config$user_browser,
-  chrome = reg_paths$chrome,
-  ff = reg_paths$ff,
-  ie = reg_paths$ie,
-  electron = config$nativefier) {
-
-  if (user_browser == "electron") {
-    electron <- gsub("/", "\\\\", electron)
-
-  } else if (user_browser == "chrome") {
-    if (chrome != "none") {
-      chrome <- gsub("\\\\", "/", file.path(chrome, "chrome.exe", fsep = "\\"))
-      options(browser = chrome)
-      launch_browser = TRUE
-    }
-
-  } else if (user_browser == "firefox") {
-    if (ff != "none") {
-      ff <- gsub("\\\\", "/", file.path(ff, "firefox.exe", fsep = "\\"))
-      options(browser = ff)
-      launch_browser = TRUE
-    }
-
-  } else if (user_browser == "ie") {
-    if (ie != "none") {
-      ie <- gsub("\\\\", "/", ie)
-      options(browser = ie)
-      launch_browser = TRUE
-    }
-  }
-
-  # If a repo has been provided, use the app in your package
-  if (config$app_repo[[1]] != "none") {
-    app_path <- file.path(system.file(package = config$appname), "app")
-
-    # flexdashboard
-    if (config$flex_file != "none") {
-      if (Sys.getenv("RSTUDIO_PANDOC") == "") {
-        Sys.setenv(RSTUDIO_PANDOC = gsub("\\\\", "/", reg_paths$pandoc))
-      }
-      rmarkdown::run(
-        file = file.path(app_path, config$flex_file),
-        shiny_args = list(
-          host = '0.0.0.0',
-          launch.browser = launch_browser,
-          port = 1984
-        )
-      )
-
-      # Shiny
-    } else {
-      shiny::runApp(app_path, launch.browser = launch_browser, port = 1984)
-    }
-
-  } else {
-    # flexdashboard
-    if (config$flex_file != "none") {
-      if (Sys.getenv("RSTUDIO_PANDOC") == "") {
-        Sys.setenv(RSTUDIO_PANDOC = gsub("\\\\", "/", reg_paths$pandoc))
-      }
-
-      rmarkdown::run(
-        file = paste0("./", config$flex_file),
-        shiny_args = list(
-          host = '0.0.0.0',
-          launch.browser = launch_browser,
-          port = 1984
-        )
-      )
-
-      # Shiny
-    } else {
-      if (user_browser == "electron") {
-        # start app
-      	# applibpath variable is set in package_manager.R
-        # OPT: use `dput` to copy whole .libPaths() and .GlobalEnv contents
-        system(sprintf('R -e ".libPaths(c(\'%s\', .libPaths())); shiny::runApp(\'./\', port=1984)"', applibpath), wait = FALSE)
-        # start electron
-        system(sprintf('cmd /C "%s"', electron), wait = FALSE)
-
-      } else {
-        shiny::runApp("./", launch.browser = launch_browser, port = 1984)
-      }
-    }
-  }
-}
-
-start_app()
+# app launching code
+config <- jsonlite::fromJSON("utils/config.cfg")
+reg_paths <- jsonlite::fromJSON("utils/regpaths.json")
+
+# This function is used to apply the web browser configuration and registry
+# information on app start up. If a user does not have the user browser,
+# their defult browser will be used.
+start_app <- function(
+  app_name = config$appname,
+  user_browser = config$user_browser,
+  chrome = reg_paths$chrome,
+  ff = reg_paths$ff,
+  ie = reg_paths$ie,
+  electron = config$nativefier) {
+
+  if (user_browser == "electron") {
+    electron <- gsub("/", "\\\\", electron)
+
+  } else if (user_browser == "chrome") {
+    if (chrome != "none") {
+      chrome <- gsub("\\\\", "/", file.path(chrome, "chrome.exe", fsep = "\\"))
+      options(browser = chrome)
+      launch_browser = TRUE
+    }
+
+  } else if (user_browser == "firefox") {
+    if (ff != "none") {
+      ff <- gsub("\\\\", "/", file.path(ff, "firefox.exe", fsep = "\\"))
+      options(browser = ff)
+      launch_browser = TRUE
+    }
+
+  } else if (user_browser == "ie") {
+    if (ie != "none") {
+      ie <- gsub("\\\\", "/", ie)
+      options(browser = ie)
+      launch_browser = TRUE
+    }
+  }
+
+  # If a repo has been provided, use the app in your package
+  if (config$app_repo[[1]] != "none") {
+    app_path <- file.path(system.file(package = config$appname), "app")
+
+    # flexdashboard
+    if (config$flex_file != "none") {
+      if (Sys.getenv("RSTUDIO_PANDOC") == "") {
+        Sys.setenv(RSTUDIO_PANDOC = gsub("\\\\", "/", reg_paths$pandoc))
+      }
+      rmarkdown::run(
+        file = file.path(app_path, config$flex_file),
+        shiny_args = list(
+          host = '0.0.0.0',
+          launch.browser = launch_browser,
+          port = 1984
+        )
+      )
+
+      # Shiny
+    } else {
+      shiny::runApp(app_path, launch.browser = launch_browser, port = 1984)
+    }
+
+  } else {
+    # flexdashboard
+    if (config$flex_file != "none") {
+      if (Sys.getenv("RSTUDIO_PANDOC") == "") {
+        Sys.setenv(RSTUDIO_PANDOC = gsub("\\\\", "/", reg_paths$pandoc))
+      }
+
+      rmarkdown::run(
+        file = paste0("./", config$flex_file),
+        shiny_args = list(
+          host = '0.0.0.0',
+          launch.browser = launch_browser,
+          port = 1984
+        )
+      )
+
+      # Shiny
+    } else {
+      if (user_browser == "electron") {
+        # start app
+      	# applibpath variable is set in package_manager.R
+        # OPT: use `dput` to copy whole .libPaths() and .GlobalEnv contents
+        system(sprintf('R -e ".libPaths(c(\'%s\', .libPaths())); shiny::runApp(\'./\', port=1984)"', applibpath), wait = FALSE)
+        # start electron
+        system(sprintf('cmd /C "%s"', electron), wait = FALSE)
+
+      } else {
+        shiny::runApp("./", launch.browser = launch_browser, port = 1984)
+      }
+    }
+  }
+}
+
+start_app()
diff --git a/inst/installation/package_manager.R b/inst/installation/package_manager.R
index 15b652c..843ceb4 100644
--- a/inst/installation/package_manager.R
+++ b/inst/installation/package_manager.R
@@ -70,7 +70,7 @@ appexit_msg <- tryCatch({
   close(pb)
 
   # Start the app
-  source(file.path(appwd, "utils/app.R"))
+  source(file.path(appwd, "utils/launch_app.R"))
 
   "application terminated normally"
 },
diff --git a/man/copy_installation.Rd b/man/copy_installation.Rd
index 6a57561..aced8a7 100644
--- a/man/copy_installation.Rd
+++ b/man/copy_installation.Rd
@@ -15,7 +15,7 @@ copy_installation(app_dir = getwd(), overwrite = TRUE)
 This function moves files stored in \code{system.file('installation', package = 'RInno')} to \code{app_dir}:
 \itemize{
   \item Icons for installer and app, \emph{setup.ico}, \emph{default.ico} and \emph{default.png}.
-  \item Files that manage app start up, \emph{utils/package_manager.R} and \emph{utils/app.R}.
+  \item Files that manage app start up, \emph{utils/package_manager.R} and \emph{utils/launch_app.R}.
   \item First/last page of the installation wizard, \emph{infobefore.txt} and \emph{infoafter.txt}.
   \item Batch support files, \emph{utils/wsf/run.wsf}, \emph{utils/wsf/js/run.js}, \emph{utils/wsf/js/json2.js}, and \emph{utils/wsf/js/JSON.minify.js}.
 }
diff --git a/man/cran_version.Rd b/man/cran_version.Rd
deleted file mode 100644
index 7a6784d..0000000
--- a/man/cran_version.Rd
+++ /dev/null
@@ -1,23 +0,0 @@
-% Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/utils.R
-\name{cran_version}
-\alias{cran_version}
-\title{Check CRAN for package version}
-\usage{
-cran_version(pkg_name, cran_url = "http://cran.r-project.org/package=")
-}
-\arguments{
-\item{pkg_name}{String. Package name as published on CRAN.}
-
-\item{cran_url}{String. First part of the cannonical form of a package website on CRAN.}
-}
-\value{
-The package's version as a \code{numeric_version}.
-}
-\description{
-Check CRAN for package version
-}
-\examples{
-cran_version("shiny")
-}
-\keyword{internal}
diff --git a/man/create_app.Rd b/man/create_app.Rd
index 3e6334f..4ea3cdd 100644
--- a/man/create_app.Rd
+++ b/man/create_app.Rd
@@ -7,8 +7,8 @@
 create_app(app_name = "myapp", app_dir = getwd(),
   dir_out = "RInno_installer", pkgs = c("jsonlite", "shiny",
   "magrittr"), pkgs_path = "bin", repo = "https://cran.rstudio.com",
-  remotes = "none", app_repo_url = "none", auth_user = "none",
-  auth_pw = "none", auth_token = github_pat(),
+  remotes = "none", locals = NULL, app_repo_url = "none",
+  auth_user = "none", auth_pw = "none", auth_token = github_pat(),
   user_browser = "electron", include_R = FALSE,
   include_Pandoc = FALSE, include_Chrome = FALSE,
   include_Rtools = FALSE, R_version = paste0(">=", R.version$major,
@@ -31,6 +31,8 @@ create_app(app_name = "myapp", app_dir = getwd(),
 
 \item{remotes}{Character vector of GitHub repository addresses in the format \code{username/repo[/subdir][\@ref|#pull]} for GitHub package dependencies.}
 
+\item{locals}{Character vector of local package dependencies. Deprecated as of v1.0.0. Use \code{pkgs} instead.}
+
 \item{app_repo_url}{Repository address for continuous installations in the format \code{"https://bitbucket.org/username/repo"} (\code{repo = app_name}). Only Bitbucket and GitHub repositories are supported.}
 
 \item{auth_user}{Bitbucket username. It is recommended to create a read-only account for each app.  Support for OAuth 2 and tokens is in the works.}
@@ -70,7 +72,7 @@ This function manages installation and app start up. To accept all defaults, jus
 Creates the following files in \code{app_dir}:
 \itemize{
   \item Icons for installer and app, \emph{setup.ico} and \emph{default.ico} respectively.
-  \item Files that manage app start up, \emph{utils/package_manager.R}, \emph{utils/ensure.R}, and \emph{utils/app.R}.
+  \item Files that manage app start up, \emph{utils/package_manager.R}, \emph{utils/ensure.R}, and \emph{utils/launch_app.R}.
   \item First/last page of the installer, \emph{infobefore.txt} and \emph{infoafter.txt}.
   \item Batch support files, \emph{utils/wsf/run.wsf}, \emph{utils/wsf/js/run.js}, \emph{utils/wsf/js/json2.js}, \emph{utils/wsf/js/JSON.minify.js}.
   \item A configuration file, \emph{config.cfg}. See \code{\link{create_config}} for details.
diff --git a/man/create_config.Rd b/man/create_config.Rd
index 5f1cc4c..31d5e7e 100644
--- a/man/create_config.Rd
+++ b/man/create_config.Rd
@@ -8,8 +8,7 @@ create_config(app_name, app_dir = getwd(), pkgs = c("jsonlite",
   "remotes", "magrittr"), pkgs_path = "library", remotes = "none",
   repo = "https://cran.rstudio.com", error_log = "error.log",
   app_repo_url = "none", auth_user = "none", auth_pw = "none",
-  auth_token = "none", user_browser = "electron",
-  ping_site = "www.ficonsulting.com")
+  auth_token = "none", user_browser = "electron")
 }
 \arguments{
 \item{app_name}{The name of the app. It will be displayed throughout the installer's window titles, wizard pages, and dialog boxes. See \href{http://www.jrsoftware.org/ishelp/topic_setup_appname.htm}{[Setup]:AppName} for details. For continuous installations, \code{app_name} is used to check for an R package of the same name, and update it. The Continuous Installation vignette has more details.}
@@ -35,8 +34,6 @@ create_config(app_name, app_dir = getwd(), pkgs = c("jsonlite",
 \item{auth_token}{To install from a private Github repo, generate a personal access token (PAT) in \url{https://github.com/settings/tokens} and supply to this argument. This is safer than using a password because you can easily delete a PAT without affecting any others.}
 
 \item{user_browser}{Character for the default browser. Options include "chrome", "firefox", and "ie."}
-
-\item{ping_site}{URL of a site to ping to check internet connectivity. Defaults to "www.ficonsulting.com".}
 }
 \value{
 A json file, \emph{config.cfg}, in \code{app_dir}/utils.
diff --git a/man/grapes-greater-than-grapes.Rd b/man/grapes-greater-than-grapes.Rd
index 6a31ef2..435f45e 100644
--- a/man/grapes-greater-than-grapes.Rd
+++ b/man/grapes-greater-than-grapes.Rd
@@ -15,5 +15,5 @@ lhs \%>\% rhs
 magrittr Pipes
 }
 \seealso{
-\code{\link[magrittr]{\%>\%}}
+\code{\link[magrittr]{magrittr}}
 }
diff --git a/man/nativefy_app.Rd b/man/nativefy_app.Rd
index eb3ada1..0efbf22 100644
--- a/man/nativefy_app.Rd
+++ b/man/nativefy_app.Rd
@@ -1,5 +1,5 @@
 % Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/nativefier_app.R
+% Please edit documentation in R/nativefy_app.R
 \name{nativefy_app}
 \alias{nativefy_app}
 \title{Package app into electron with nativefier}
diff --git a/man/standardize_pkgs.Rd b/man/standardize_pkgs.Rd
index d8eabc5..248ea2f 100644
--- a/man/standardize_pkgs.Rd
+++ b/man/standardize_pkgs.Rd
@@ -4,15 +4,13 @@
 \alias{standardize_pkgs}
 \title{Standardize package dependencies}
 \usage{
-standardize_pkgs(pkgs, check_version = FALSE, string = FALSE)
+standardize_pkgs(pkgs)
 }
 \arguments{
 \item{pkgs}{Processes \code{pkgs}, and \code{pkgs}, arguments of \code{\link{create_config}} and \code{\link{create_app}}.}
-
-\item{check_version}{Boolean. If true, check to make sure the package version is not ahead of CRAN.}
 }
 \value{
-Package dependency list with version numbers and inequalities. Defaults to \code{paste0(">=", packageVersion(pkg))}.
+Package dependency list
 }
 \description{
 Standardizes (named or not) character vectors of package dependencies and formats it for config.cfg.
diff --git a/tests/testthat/test-standardize_pkgs.R b/tests/testthat/test-standardize_pkgs.R
index 27f4e98..a234abc 100644
--- a/tests/testthat/test-standardize_pkgs.R
+++ b/tests/testthat/test-standardize_pkgs.R
@@ -5,14 +5,6 @@ test_that("Package dependencies are installed", {
   expect_error(standardize_pkgs(c(test = ">1.0")))
 })
 
-test_that("Logical operators are valid", {
-  expect_error(standardize_pkgs(c(shiny = "=<1.0.5")))
-})
-
-test_that("Numeric version numbers are valid", {
-  expect_error(standardize_pkgs(c(shiny = "haha you lose")))
-})
-
 test_that("Packages are standardized consistently", {
   pkgs1 <- standardize_pkgs(c("shiny", "rmarkdown", "jsonlite"))
   pkgs2 <- standardize_pkgs(c("shiny", "rmarkdown", jsonlite = ">=1.5"))
diff --git a/vignettes/Introduction.Rmd b/vignettes/Introduction.Rmd
index 53bc855..b6929ef 100644
--- a/vignettes/Introduction.Rmd
+++ b/vignettes/Introduction.Rmd
@@ -118,7 +118,7 @@ create_app(
 `create_app` passes its arguments to most of the other support functions in RInno. You can (and probably should) specify most things there and they will get passed on. Alternatively, you can provide instructions directly to those support functions like this:
 
 ```
-# Copy installation scripts (JavaScript, icons, infobefore.txt, package_manager.R, app.R)
+# Copy installation scripts (JavaScript, icons, infobefore.txt, package_manager.R, launch_app.R)
 copy_installation(app_dir = "my/app/path")
 
 # If your users need R installed: