Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,132 @@ jobs:
- name: Verify installation
run: vp --version

test-reuse-installed-version:
# Reproduces https://github.com/voidzero-dev/setup-vp/issues/145. Two setup
# steps share an installation, as jobs on a persistent runner would.
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
layout: [home, split]
include:
- os: ubuntu-latest
layout: xdg
runs-on: ${{ matrix.os }}
timeout-minutes: 10
env:
VP_PR_VERSION: ""
REUSE_LAYOUT: ${{ matrix.layout }}
steps:
- uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2

- name: Prepare project and record CLI downloads
shell: bash
run: |
mkdir -p "$RUNNER_TEMP/reuse-project" "$RUNNER_TEMP/reuse-curl"
echo '{"name":"reuse-project","private":true,"devDependencies":{"vite-plus":"0.3.0"}}' > "$RUNNER_TEMP/reuse-project/package.json"
reuse_root="$RUNNER_TEMP/setup-vp-reuse"
# The Windows installer passes this path to cmd.exe for mklink.
if [ "$RUNNER_OS" = "Windows" ]; then reuse_root="$(cygpath -w "$reuse_root")"; fi
{
case "$REUSE_LAYOUT" in
home)
echo "VP_HOME=$reuse_root"
echo "SETUP_VP_REUSE_DATA=$reuse_root"
;;
split)
echo "VP_HOME="
echo "VP_DATA_DIR=$reuse_root"
echo "VP_BIN_DIR=$reuse_root-bin"
echo "VP_CACHE_DIR=$reuse_root-cache"
echo "SETUP_VP_REUSE_DATA=$reuse_root"
;;
xdg)
echo "VP_HOME="
echo "XDG_DATA_HOME=$reuse_root-xdg"
echo "SETUP_VP_REUSE_DATA=$reuse_root-xdg/vite-plus"
;;
esac
echo "SETUP_VP_REUSE_CURL=$(command -v curl)"
echo "SETUP_VP_REUSE_DOWNLOADS=$RUNNER_TEMP/setup-vp-reuse-downloads.log"
} >> "$GITHUB_ENV"
: > "$RUNNER_TEMP/setup-vp-reuse-downloads.log"
cat > "$RUNNER_TEMP/reuse-curl/curl" <<'EOF'
#!/bin/bash
set -euo pipefail
for arg in "$@"; do
case "$arg" in
*/vite-plus-cli-*.tgz)
printf '%s\n' "$arg" >> "$SETUP_VP_REUSE_DOWNLOADS"
;;
esac
done
exec "$SETUP_VP_REUSE_CURL" "$@"
EOF
chmod +x "$RUNNER_TEMP/reuse-curl/curl"
echo "$RUNNER_TEMP/reuse-curl" >> "$GITHUB_PATH"

- name: Setup Vite+ for the first time
id: first-setup
uses: ./
with:
working-directory: ${{ runner.temp }}/reuse-project
node-version: "22.23.1"
run-install: false
cache: false

- name: Check the first installation and start recording reuse
shell: bash
working-directory: ${{ runner.temp }}/reuse-project
env:
INSTALLED_VERSION: ${{ steps.first-setup.outputs.version }}
run: |
test "$INSTALLED_VERSION" = "0.3.0"
binary=vp
if [ "$RUNNER_OS" = "Windows" ]; then binary=vp.exe; fi
test -x "$SETUP_VP_REUSE_DATA/current/bin/$binary"
test -f "$SETUP_VP_REUSE_DATA/0.3.0/node_modules/vite-plus/package.json"
test "$(node --version)" = "v22.23.1"
# Check that the recorder sees real downloads before relying on it.
# Windows downloads through PowerShell; install.log covers that path.
if [ "$RUNNER_OS" != "Windows" ]; then test -s "$SETUP_VP_REUSE_DOWNLOADS"; fi
echo "CLI downloads during the first setup:"
cat "$SETUP_VP_REUSE_DOWNLOADS"
# The installer truncates this log when it runs its own vp install.
python -c 'import os, sys; print(os.stat(sys.argv[1]).st_mtime_ns)' "$SETUP_VP_REUSE_DATA/0.3.0/install.log" > "$RUNNER_TEMP/reuse-install-log-before"
: > "$SETUP_VP_REUSE_DOWNLOADS"

- name: Setup the same Vite+ version again
id: second-setup
uses: ./
with:
working-directory: ${{ runner.temp }}/reuse-project
node-version: "22.23.1"
run-install: false
cache: false

- name: Verify the second setup reuses the installation
shell: bash
working-directory: ${{ runner.temp }}/reuse-project
env:
INSTALLED_VERSION: ${{ steps.second-setup.outputs.version }}
run: |
test "$INSTALLED_VERSION" = "0.3.0"
test "$(node --version)" = "v22.23.1"
failed=0
if [ -s "$SETUP_VP_REUSE_DOWNLOADS" ]; then
echo "::error::The second setup downloaded the installed Vite+ version again (issue #145)."
cat "$SETUP_VP_REUSE_DOWNLOADS"
failed=1
fi
python -c 'import os, sys; print(os.stat(sys.argv[1]).st_mtime_ns)' "$SETUP_VP_REUSE_DATA/0.3.0/install.log" > "$RUNNER_TEMP/reuse-install-log-after"
if ! cmp -s "$RUNNER_TEMP/reuse-install-log-before" "$RUNNER_TEMP/reuse-install-log-after"; then
echo "::error::The second setup rewrote install.log by running the wrapper install again (issue #145)."
diff -u "$RUNNER_TEMP/reuse-install-log-before" "$RUNNER_TEMP/reuse-install-log-after" || true
failed=1
fi
exit "$failed"

test-preview-build:
# End-to-end coverage for the commit-pinned install path: resolve the
# newest pkg.pr.new preview build (0.0.0-commit.<sha>) from the registry
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,18 @@ Omitting both `node-version` and `node-version-file` leaves the session without

## Caching

### Reuse an installed version

On runners that retain the Vite+ installation, the GitHub Action reuses an
active exact version when its files, shims, and Node.js manager configuration
pass the reuse checks. This applies to versions that support `VP_DUMP_DIRS`
(`0.3.0` and later). Node.js setup and the remaining action steps still run.

The action runs the installer when it needs to change versions or repair the
installation. It also runs the installer for dist-tags such as `latest`, version
ranges, preview builds, and older releases. Reuse works with `cache: false` and
does not restore an installation onto a fresh runner.

### Dependency Cache

When `cache: true` is set, the action additionally caches project dependencies by auto-detecting your lock file:
Expand Down
128 changes: 64 additions & 64 deletions dist/index.mjs

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions src/install-viteplus.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { exec } from "@actions/exec";
import { addPath, warning } from "@actions/core";
import { writeFileSync } from "node:fs";
import { installVitePlus } from "./install-viteplus.js";
import { findReusableVitePlus } from "./reuse-viteplus.js";
import type { Inputs } from "./types.js";

vi.mock("@actions/core", () => ({
Expand All @@ -15,6 +16,8 @@ vi.mock("@actions/exec", () => ({
exec: vi.fn(),
}));

vi.mock("./reuse-viteplus.js", () => ({ findReusableVitePlus: vi.fn() }));

vi.mock("node:timers/promises", () => ({
setTimeout: vi.fn().mockResolvedValue(undefined),
}));
Expand Down Expand Up @@ -86,6 +89,17 @@ describe("installVitePlus", () => {
expect(addPath).toHaveBeenCalledWith("/test/data/bin");
});

it("reuses a validated installation and puts it first on PATH", async () => {
vi.stubEnv("PATH", "/other/bin:/existing/bin");
vi.mocked(findReusableVitePlus).mockReturnValue("/existing/bin");

await installVitePlus({ ...baseInputs, version: "0.3.0", nodeManager: true });

expect(findReusableVitePlus).toHaveBeenCalledWith("0.3.0", true);
expect(addPath).toHaveBeenCalledWith("/existing/bin");
expect(exec).not.toHaveBeenCalled();
});

it("should fall back to the legacy bin for Vite+ releases without VpDirs", async () => {
vi.stubEnv("HOME", "/home/runner");
vi.stubEnv("PATH", "/usr/bin");
Expand Down
10 changes: 10 additions & 0 deletions src/install-viteplus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import type { Inputs } from "./types.js";
import { DISPLAY_NAME } from "./types.js";
import { getVitePlusHome } from "./utils.js";
import { findReusableVitePlus } from "./reuse-viteplus.js";

// Try each group's URLs in order, for up to N rounds per group (max attempts
// per group = rounds * URLs). Two rounds × two URLs = 4 attempts, ~1 minute
Expand All @@ -24,6 +25,15 @@ const INSTALL_RETRY_DELAY_MS = 2000;
export async function installVitePlus(inputs: Inputs): Promise<void> {
const { version } = inputs;

const existingBin = findReusableVitePlus(version, inputs.nodeManager);
if (existingBin) {
// Prepend even when already present later on PATH: another installation
// must not shadow the version that passed the reuse checks.
addPath(existingBin);
info(`Reusing ${DISPLAY_NAME}@${version} from ${existingBin}`);
return;
}

info(`Installing ${DISPLAY_NAME}@${version}...`);

// TODO: Remove VITE_PLUS_VERSION once vite-plus versions before the VP_* env var
Expand Down
Loading
Loading