Summary
setup-vp re-runs the Vite+ installer on every job, and the installer has no path that notices the requested version is already installed. On a runner whose home directory persists across jobs (Blacksmith sticky disks, most self-hosted runners), every job pays the same ~3s to download and re-install a vp that is already there. The Node half is already idempotent: vp env use on a present runtime returns in a few milliseconds. Only the installer is not.
Measurement
Repository: a pnpm monorepo, setup-vp@v1.18.0 (the install path is unchanged on main at 0499fae), Vite+ 0.3.0 resolved from package.json, Node 22.23.1 resolved from package.json, cache: false, run-install: false. Runner: blacksmith-4vcpu-ubuntu-2404. vp env keeps its own runtimes under $VP_HOME/js_runtime, so the pinned 22.23.1 is a download on every fresh runner whatever Node the image ships.
One job, timestamps from the log (2026-09-10):
02:33:12.740 curl -fsSL ... /v0.3.0/packages/cli/install.sh -o "$installer_file"; source "$installer_file"
02:33:12.767 Setting up VITE+...
02:33:15.639 ✔ VITE+ successfully installed! <- 2.9s
02:33:15.644 Setting up Node.js 22.23.1 via vp env use...
02:33:15.647 Installing Node.js v22.23.1...
02:33:16.260 Using Node.js v22.23.1 (resolved from 22.23.1) <- 0.6s
##[end-action id=__self.setup-vp;... duration_ms=3613]
Across our jobs the step runs 3.6s to 6.1s: 2.8s to 2.9s in the installer, 0.6s to 1.1s for the Node download, the rest in vp env use and version reporting. Our PR workflow alone starts about 240 runs a day, each with up to six jobs that run this step; even discounting runs cancelled by a newer push, that is on the order of a thousand installs of the same version a day, roughly an hour of runner time spent re-installing a binary the runner already has.
For comparison, once the version directory exists, vp env use 22.23.1 on a present runtime takes about 6 ms, and a pnpm store on a persistent disk turns our workspace install into a link-only 4s. The installer is now the largest fixed cost in the step.
What happens today, from the source
setup-vp (src/install-viteplus.ts, installVitePlus) fetches install.sh for the resolved tag and sources it, unconditionally; there is no check of an existing vp at the target directory and no use of @actions/tool-cache. The only vp --version call in src/index.ts runs after the install, to report the version.
install.sh (packages/cli/install.sh on main at 31b0922a), in main():
get_version_from_metadata fetches the registry metadata for VP_VERSION and sets RESOLVED_VERSION (network).
download_and_extract fetches @voidzero-dev/vite-plus-cli-<platform>-<version>.tgz into a temp dir (network) and copies the binary into $INSTALL_DIR/$VP_VERSION/bin.
- It rewrites
$INSTALL_DIR/$VP_VERSION/package.json (the vp-global wrapper pinned to pnpm@10.33.0) and runs vp install there to install the vite-plus JS package and its transitive dependencies (network).
- It repoints
$INSTALL_DIR/current and sets up shims.
None of these steps compares the resolved version against what is already in $INSTALL_DIR. detect_previous_install_dir / is_vite_plus_install_dir exist, but they detect an older layout to migrate, not a current same-version install to keep. So a persisted $VP_HOME (we can mount ~/.local/share/vite-plus as a sticky disk) does not save anything: the job still downloads the tarball, re-copies the binary and re-runs the wrapper install on top of an identical tree.
Ask
Either half would do; both would be ideal.
- Installer: after
RESOLVED_VERSION is known, if $INSTALL_DIR/$RESOLVED_VERSION/bin/vp --version reports that version and the wrapper install is complete (the current symlink and node_modules/vite-plus present), skip steps 2 and 3 and go straight to current/shim/Node setup. That makes a persisted $VP_HOME free for GitHub, the GitLab template, and any other CI that keeps a home directory, and it also makes re-running the installer locally a no-op.
- setup-vp: before running the installer, look for
vp at the target bin directory and skip the installer when its --version matches the resolved version; and, for GitHub-hosted runners without a persistent home, cache the installed version directory through @actions/tool-cache so a warm runner skips the network entirely.
Happy to try a prerelease on our runners and report timings.
Summary
setup-vpre-runs the Vite+ installer on every job, and the installer has no path that notices the requested version is already installed. On a runner whose home directory persists across jobs (Blacksmith sticky disks, most self-hosted runners), every job pays the same ~3s to download and re-install avpthat is already there. The Node half is already idempotent:vp env useon a present runtime returns in a few milliseconds. Only the installer is not.Measurement
Repository: a pnpm monorepo,
setup-vp@v1.18.0(the install path is unchanged onmainat 0499fae), Vite+ 0.3.0 resolved frompackage.json, Node 22.23.1 resolved frompackage.json,cache: false,run-install: false. Runner:blacksmith-4vcpu-ubuntu-2404.vp envkeeps its own runtimes under$VP_HOME/js_runtime, so the pinned 22.23.1 is a download on every fresh runner whatever Node the image ships.One job, timestamps from the log (2026-09-10):
Across our jobs the step runs 3.6s to 6.1s: 2.8s to 2.9s in the installer, 0.6s to 1.1s for the Node download, the rest in
vp env useand version reporting. Our PR workflow alone starts about 240 runs a day, each with up to six jobs that run this step; even discounting runs cancelled by a newer push, that is on the order of a thousand installs of the same version a day, roughly an hour of runner time spent re-installing a binary the runner already has.For comparison, once the version directory exists,
vp env use 22.23.1on a present runtime takes about 6 ms, and a pnpm store on a persistent disk turns our workspace install into a link-only 4s. The installer is now the largest fixed cost in the step.What happens today, from the source
setup-vp(src/install-viteplus.ts,installVitePlus) fetchesinstall.shfor the resolved tag and sources it, unconditionally; there is no check of an existingvpat the target directory and no use of@actions/tool-cache. The onlyvp --versioncall insrc/index.tsruns after the install, to report the version.install.sh(packages/cli/install.shonmainat 31b0922a), inmain():get_version_from_metadatafetches the registry metadata forVP_VERSIONand setsRESOLVED_VERSION(network).download_and_extractfetches@voidzero-dev/vite-plus-cli-<platform>-<version>.tgzinto a temp dir (network) and copies the binary into$INSTALL_DIR/$VP_VERSION/bin.$INSTALL_DIR/$VP_VERSION/package.json(thevp-globalwrapper pinned topnpm@10.33.0) and runsvp installthere to install thevite-plusJS package and its transitive dependencies (network).$INSTALL_DIR/currentand sets up shims.None of these steps compares the resolved version against what is already in
$INSTALL_DIR.detect_previous_install_dir/is_vite_plus_install_direxist, but they detect an older layout to migrate, not a current same-version install to keep. So a persisted$VP_HOME(we can mount~/.local/share/vite-plusas a sticky disk) does not save anything: the job still downloads the tarball, re-copies the binary and re-runs the wrapper install on top of an identical tree.Ask
Either half would do; both would be ideal.
RESOLVED_VERSIONis known, if$INSTALL_DIR/$RESOLVED_VERSION/bin/vp --versionreports that version and the wrapper install is complete (thecurrentsymlink andnode_modules/vite-pluspresent), skip steps 2 and 3 and go straight tocurrent/shim/Node setup. That makes a persisted$VP_HOMEfree for GitHub, the GitLab template, and any other CI that keeps a home directory, and it also makes re-running the installer locally a no-op.vpat the target bin directory and skip the installer when its--versionmatches the resolved version; and, for GitHub-hosted runners without a persistent home, cache the installed version directory through@actions/tool-cacheso a warm runner skips the network entirely.Happy to try a prerelease on our runners and report timings.