This content is 'api', but the bundle is identified as 'shiny'
slodge-work opened this issue · comments
As part of my mass upgrade of lots of projects and our CI/CD system to R4.3, I've also been upgrading rsconnect too. Similar to #998 one of our projects has upgraded rsconnect from 0.8.29 to 1.1.0
On deployment we now see an error of:
List of 3 $ code : int 48 $ error : chr "you cannot change the type of content once deployed. This content is 'api', but the bundle is identified as 'sh"| __truncated__ $ payload: NULL
This is possibly linked to #942 (but the reverse direction)
Our repo structure is:
/
/package # contains DESCRIPTION, R, etc - shared code between shiny and plumber
/plumber # contains a plumber.R file
/renv # renv is used at the top level of the repo
/shiny # contains a shiny app
Our CI/CD system has code like:
library(R.InHouseDeploymentHelpers)
write_manifest("shiny/")
deploy_app(
"name_for_shiny_content",
"shiny/"
)
write_manifest("plumber/", "plumber.R")
deploy_app(
"name_for_plumber_content",
"plumber/"
)
This previously has worked well for us - we've got repos with several Markdown reports, several shiny apps and a plumber api all deployed from the same CD script.
I see appMode
is available now... I'll give it a try...
I just tried the following to mirror what you described, but the manifests appear correct.
# Create a new project and launch a separate RStudio session.
usethis::create_project("~/Desktop/mixed-content")
# In that new project ...
unlink("R", recursive = TRUE)
dir.create("shiny")
writeLines(c(
"shinyApp('single file Shiny deploy', function(input, output) {})"
), "shiny/app.R")
dir.create("plumber")
writeLines(c(
"#* @get /",
"root <- function() { 'initial Plumber deploy' }"
), "plumber/plumber.R")
# Using the R library ...
rsconnect::writeManifest("shiny")
rsconnect::writeManifest("plumber")
jsonlite::fromJSON("shiny/manifest.json")$metadata$appmode
#> shiny
jsonlite::fromJSON("plumber/manifest.json")$metadata$appmode
#> plumber
# Using renv ...
renv::init()
renv::install(c("rsconnect","shiny","plumber"), prompt = FALSE)
rsconnect::writeManifest("shiny")
rsconnect::writeManifest("plumber")
jsonlite::fromJSON("shiny/manifest.json")$metadata$appmode
#> shiny
jsonlite::fromJSON("plumber/manifest.json")$metadata$appmode
#> plumber
Could you tell us more about the shiny/
and plumber/
directories? Could you adjust my example to recreate your scenario?
Thank you Aron.
Awesome to have a repro to play with.
It looks like the problem isn't to do with having 2 apps - it looks like this is to do with us supplying a primary doc parameter for the plumber app.
Here's what reproduces the error for me:
dir.create("shiny")
writeLines(c(
"library('shiny')",
"shinyApp('single file Shiny deploy', function(input, output) {})"
), "shiny/app.R")
dir.create("plumber")
writeLines(c(
"library('plumber')",
"#* @get /",
"root <- function() { 'initial Plumber deploy' }"
), "plumber/plumber.R")
install.packages(c("rsconnect", "shiny", "plumber"))
rsconnect::writeManifest("shiny")
rsconnect::writeManifest("plumber",appPrimaryDoc = "plumber.R")
jsonlite::fromJSON("shiny/manifest.json")$metadata$appmode
#> shiny
jsonlite::fromJSON("plumber/manifest.json")$metadata$appmode
#> shiny
This code used to work fine under rsconnect 0.x ... but I'm OK removing the parameter if it's an expected change 👍
Just to confirm no shiny needed to see this:
dir.create("plumber")
writeLines(c(
"library('plumber')",
"#* @get /",
"root <- function() { 'initial Plumber deploy' }"
), "plumber/plumber.R")
install.packages(c("rsconnect", "shiny", "plumber"))
rsconnect::writeManifest("plumber",appPrimaryDoc = "plumber.R")
jsonlite::fromJSON("plumber/manifest.json")$metadata$appmode
#> shiny
The appPrimaryDoc
is not needed for Plumber APIs. That information is necessary when you are deploying alpha.Rmd
, beta.Rmd
, and primary.Rmd
together. Maybe the alpha.Rmd
and beta.Rmd
files are included by code into primary.Rmd
. In that case, you want to use appPrimaryDoc = "primary.Rmd"
to indicate that is the document that should be rendered.
The primary document is also handy for static content, as it indicates the document to serve when a browser visits the content URL (without a filename). Loading /my-content/
would redirect to /my-content/primary.html
, for example.
In your example:
rsconect::writeManifest(appPrimaryDoc = "plumber.R")
the check for API content previously happened before consideration of appPrimaryDoc
:
The refactor to appMetadata
and inferAppMode
moved the primary doc inference ahead of the API type check. rsconnect-0.8.29 ignored appPrimaryDoc
for Plumber APIs, but I'm inclined to keep the current behavior for now.
Long story short -- you should not specify appPrimaryDoc
for Plumber APIs.
Please reopen this issue if you are unable to remove the appPrimaryDoc
argument or if you uncover other reasons for us to revisit how we infer the content type.
Adding a few more breadcrumbs for future-us:
The appPrimaryDoc
is sometimes used for Shiny applications when folks use a nonstandard filename dashboard.R
for their Shiny application.
In addition to forcing the content type, that file is renamed to app.R
during bundling:
Lines 21 to 27 in f3efe89
Breadcrumb also for my future what-if...
I think the reason why we had this plumber.R (which previously worked) was because we previously were hosting a shiny and an app in the same folder - especially so they could share a common /R folder. We since changed that so they shared a package instead (but that did require some CI/CD tooling to auto-update package versions on our side)
@slodge-work - shifting to separate directories makes many things more straightforward, but does complicate code sharing.
If you find yourself needing to handle flat directories in the future, you could also build your own appFiles
enumeration and discard the Shiny app.R
or Plumber plumber.R
as appropriate. The appMode
option is also a way to avoid content type inference.