Getting Started with courieR

courieR syncs installed R packages between R versions on the same machine. You can migrate from an old version to a new one, keep two versions in parity, or selectively push packages in either direction — all from the R console or from a point-and-click dashboard.

Installation

Install courieR from CRAN:

install.packages("courieR")

The core CLI workflow (find_routes(), manifest(), inventory(), ship()) has no Shiny dependency. The dashboard (open_hub()) requires additional packages:

install.packages(c("shiny", "bslib", "bsicons", "DT"))

Step 1 — Discover Your R Installations

find_routes() scans the system and returns a data frame of every R version it can find:

library(courieR)

routes <- find_routes()
routes
#>   version                                          rscript_path is_current
#> 1   4.4.2        C:/Program Files/R/R-4.4.2/bin/x64/Rscript.exe       TRUE
#> 2   4.3.1  C:/Users/you/AppData/Local/Programs/R/R-4.3.1/bin/...      FALSE
#> 3   4.1.3            C:/Users/you/Documents/R/R-4.1.3/bin/...      FALSE

is_current = TRUE marks the R session you are running right now.

Platform detection

Platform Locations checked
Windows HKLM/HKCU registry, %ProgramFiles%\R, %LOCALAPPDATA%\Programs\R, %USERPROFILE%\Documents\R, rig
macOS /Library/Frameworks/R.framework, ~/Library/Frameworks/R.framework, Homebrew (/opt/homebrew, /usr/local), rig
Linux /opt/R (rig system), ~/.local/share/rig/R (rig user), conda envs, system Rscript on $PATH

To include a non-standard path, pass it explicitly:

routes <- find_routes(search_paths = "/opt/custom-r/bin/Rscript")

CLI Workflow

The four core functions form a pipeline:

find_routes()  →  manifest()  →  inventory()  →  ship()
   discover        scan            compare         migrate

Step 2 — Scan a library with manifest()

manifest() runs a subprocess under a given Rscript and returns every installed package with its version and source:

# scan the first (newest) R
src_pkgs <- manifest(rscript_path = routes$rscript_path[1])
head(src_pkgs[, c("package", "version", "source")])
#>     package version source
#> 1     broom   1.0.7   CRAN
#> 2    callr   3.7.6   CRAN
#> 3   courieR   0.2.0 GitHub
#> 4   ggplot2   3.5.1   CRAN
#> 5      glue   1.8.0   CRAN
#> 6   stringr   1.5.1   CRAN

Calling manifest() with no arguments scans the library of the current R session:

my_pkgs <- manifest()
nrow(my_pkgs)
#> [1] 312

Base and recommended packages are included in the raw manifest. Filter them out before comparison:

user_pkgs <- src_pkgs[is.na(src_pkgs$priority) | !(src_pkgs$priority %in% c("base", "recommended")), ]

Step 3 — Compare two libraries with inventory()

inventory() takes two manifests and returns a classified diff:

src_pkgs <- manifest(rscript_path = routes$rscript_path[2])  # old R
tgt_pkgs <- manifest(rscript_path = routes$rscript_path[1])  # new R

comp <- inventory(src_pkgs, tgt_pkgs)

The result is a list with three elements:

# packages in source but missing from target
nrow(comp$missing)
#> [1] 47

# packages where source has a newer version
nrow(comp$outdated)
#> [1] 12

# packages at the same version in both
nrow(comp$same)
#> [1] 201

Inspect what needs to move:

comp$missing[, c("package", "version", "source")]
#>       package version  source
#>  1:    bookdown   0.39    CRAN
#>  2:  brms   2.21.0    CRAN
#>  3:   officer   0.6.6    CRAN
#>  ...

comp$outdated[, c("package", "version_src", "version_tgt", "source")]
#>    package version_src version_tgt source
#> 1:  ggplot2       3.5.1       3.4.4   CRAN
#> 2:   tibble       3.2.1       3.2.0   CRAN

Step 4 — Migrate with ship()

ship() takes a source and target Rscript path, computes the plan internally, and runs it:

result <- ship(
  source_path = routes$rscript_path[2],   # old R — package source
  target_path = routes$rscript_path[1]    # new R — install destination
)

Always dry-run first

result <- ship(
  source_path = routes$rscript_path[2],
  target_path = routes$rscript_path[1],
  dry_run = TRUE
)

# review the plan before anything is installed
print(result$plan)
#>         package action  pak_spec
#>  1:    bookdown install bookdown
#>  2:        brms install     brms
#>  3:     ggplot2 upgrade  ggplot2
#>  ...

Include version upgrades

By default, ship() only installs missing packages. Pass upgrade = TRUE to also update packages that are present but at an older version (mirrors what the Sync tab does):

result <- ship(
  source_path = routes$rscript_path[2],
  target_path = routes$rscript_path[1],
  upgrade = TRUE
)

Check results

# summary counts
result$summary
#> $installed   47
#> $upgraded    12
#> $failed       1
#> $skipped      0

# per-package results
result$results[result$results$status == "error", c("package", "message")]
#>    package                         message
#> 1:    rJava  installation of rJava failed...

Migrate GitHub and Bioconductor packages

ship() detects source-specific packages automatically. CRAN packages are reinstalled from CRAN; GitHub packages become "owner/repo" pak specs; Bioconductor packages use "bioc::pkg" specs. No extra configuration needed.

# mixed-source example — all handled automatically
result$plan[, c("package", "source", "pak_spec")]
#>    package   source        pak_spec
#> 1:  ggplot2     CRAN         ggplot2
#> 2:  courieR   GitHub  lennon-li/courieR
#> 3:    DESeq2  Bioconductor  bioc::DESeq2

Common Recipes

One-way migration (old R → new R)

library(courieR)

routes  <- find_routes()
old_r   <- routes$rscript_path[!routes$is_current][1]
new_r   <- routes$rscript_path[routes$is_current]

# dry run
ship(source_path = old_r, target_path = new_r, dry_run = TRUE, upgrade = TRUE)

# for real
result <- ship(source_path = old_r, target_path = new_r, upgrade = TRUE)
result$summary

Two-way sync (keep two R versions in parity)

r_a <- routes$rscript_path[1]
r_b <- routes$rscript_path[2]

# push everything A has to B
ship(source_path = r_a, target_path = r_b, upgrade = TRUE)

# push everything B has to A
ship(source_path = r_b, target_path = r_a, upgrade = TRUE)

Inspect before migrating

src  <- manifest(rscript_path = old_r)
tgt  <- manifest(rscript_path = new_r)
comp <- inventory(src, tgt)

# only migrate CRAN packages — skip GitHub/unknown sources
cran_missing <- comp$missing[comp$missing$source == "CRAN", ]
cat(nrow(cran_missing), "CRAN packages to install\n")

Save a manifest to disk

pkgs <- manifest(rscript_path = routes$rscript_path[1])
write.csv(pkgs[, c("package", "version", "source")], "my_packages.csv", row.names = FALSE)

Restore from a saved manifest on a new machine:

saved <- read.csv("my_packages.csv", stringsAsFactors = FALSE)
# pak installs from a character vector of package names
pak::pkg_install(saved$package)

Dashboard

If you prefer point-and-click, open_hub() launches a Shiny dashboard that wraps the same pipeline. Install the dashboard dependencies first if you haven’t already:

install.packages(c("shiny", "bslib", "bsicons", "DT"))
open_hub()

The Sync tab mirrors the ship() CLI workflow. The Advanced tab exposes manifest() output and lets you inspect any detected R installation’s full package list.


Tips