courieR syncs installed R packages between R versions on the same machine. You can migrate from an old version to a new one, or selectively push packages from a source installation into a target installation — all from the R console or from a point-and-click dashboard.
Install courieR from CRAN:
Or install the development version from GitHub:
All dependencies — including the dashboard packages — are installed automatically.
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/... FALSEis_current = TRUE marks the R session you are running
right now.
| 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:
The four core functions form a pipeline:
find_routes() → manifest() → inventory() → ship()
discover scan compare migrate
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 CRANCalling manifest() with no arguments scans the library
of the current R session:
Base and recommended packages are excluded automatically — the returned data contains only user-installed packages.
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] 201Inspect 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.x", "version.y", "source")]
#> package version.x version.y source
#> 1: ggplot2 3.5.1 3.4.4 CRAN
#> 2: tibble 3.2.1 3.2.0 CRANship()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
)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 Bulk Dispatch tab does):
# per-package outcomes
result$results
#> package status message
#> 1: bookdown success Installed
#> 2: brms success Installed
#> 3: rJava error installation of rJava failed...
# count by outcome
table(result$results$status)
#> error success
#> 1 58
# failed packages only
result$results[result$results$status == "error", ]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::DESeq2ship() (and the dashboard’s Transfer
mode selector) offers three mutually-exclusive ways to move
packages. Pass one via mode =:
| Mode | What it does | When to use |
|---|---|---|
"online" (default) |
Reinstall each package fresh via pak — latest compatible version, dependencies resolved. Pure-R and local packages are copied directly (no rebuild); only compiled packages are reinstalled, preferring pre-built binaries. | Moving between different R versions, where compiled packages must be rebuilt for the new R. |
"offline" |
Copy package files directly from the source library — fast, no internet, exact same versions. | Same R x.y series, or air-gapped machines. |
"preserve" |
Same direct copy as offline, but anything that can’t be
copied is reinstalled via pak at its exact source version. |
Same series, when you want a network safety net. |
A package’s compiled code (the
.dll/.so files under libs/) is
built against a specific R minor version’s binary
interface. Packages compiled for R 4.5.x will not reliably load under R
4.6 — they must be rebuilt. So:
offline/preserve are the
fast choice.online is safe. The
dashboard hides the copy modes for such pairs automatically.Pure-R packages (no libs/) are version-independent, so
online copies those directly even across versions and only
rebuilds the compiled minority.
The shortest path uses migrate(), which looks up
installations by version string so you don’t need to handle paths
yourself:
library(courieR)
# dry run first
migrate("4.5.2", "4.6.0", dry_run = TRUE)
# for real
result <- migrate("4.5.2", "4.6.0")
table(result$results$status)For more control — filtering by package, custom paths, or progress
callbacks — use ship() directly:
ship() always moves packages one way, from
source_path into target_path. To bring two
same-version installations into line with each other, run it twice,
swapping the roles:
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)hub() launches a Shiny dashboard that wraps the same
pipeline:
The Bulk Dispatch tab compares two R libraries and ships all missing or outdated packages in one operation.
Bulk Dispatch: detect R installations, click Compare, review the colour-coded summary chips and package table, then Ship. The log pane on the right shows real-time progress; a live hero panel counts delivered packages during the ship.
Workflow:
The first sync on a machine can take 1–2 minutes while pak builds its metadata cache. Subsequent syncs are faster.
The Custom Dispatch tab lets you cherry-pick individual packages instead of bulk-shipping everything.
Custom Dispatch: colour-coded filter chips narrow the table to the statuses you care about; check individual packages and press Ship. The Repo column highlights packages with an unknown source in red.
Workflow:
The Browse and Manifest panels
under the Tools tab expose manifest()
output and let you inspect any detected R installation’s full package
list.
hub() is a short alias for hub() — both
launch the dashboardship() uses pak under the hood, which
resolves dependencies automaticallymode = "offline" when you have no internet access —
courieR copies package directories directly. Packages without a local
source path are reported as skippedmode = "preserve" to keep exact version numbers;
courieR copies where possible and pins the version in the pak spec
otherwisepak installed, because installation runs
under the target R processGITHUB_PAT to be set in the environmentresult$results — the
message column has the pak errordry_run = TRUE before any large migration to review
the plan without installing anythingfind_routes() may only detect the
R on $PATH; pass additional paths via
search_paths if needed