Skip to content
Merged
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ When `cache: true` is set, the action additionally caches project dependencies b

The dependency cache key format is: `vite-plus-{OS}-{arch}-{pm}-{lockfile-hash}`

When `cache-dependency-path` points to a lock file in a subdirectory, the action resolves the package-manager cache directory from that lock file's directory.

## Example Workflow

```yaml
Expand Down
150 changes: 75 additions & 75 deletions dist/index.mjs

Large diffs are not rendered by default.

15 changes: 11 additions & 4 deletions src/cache-restore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,30 @@ import { warning, info, debug, saveState, setOutput } from "@actions/core";
import { arch, platform } from "node:os";
import type { Inputs } from "./types.js";
import { State, Outputs } from "./types.js";
import { detectLockFile, getCacheDirectories } from "./utils.js";
import { detectLockFile, getCacheDirectories, getCacheDirectoryCwd } from "./utils.js";

export async function restoreCache(inputs: Inputs): Promise<void> {
// Detect lock file
const lockFile = detectLockFile(inputs.cacheDependencyPath);
if (!lockFile) {
warning("No lock file found. Skipping cache restore.");
const message = inputs.cacheDependencyPath
? `No lock file found for cache-dependency-path: ${inputs.cacheDependencyPath}. Skipping cache restore.`
: "No lock file found in workspace root. Skipping cache restore.";
warning(message);
setOutput(Outputs.CacheHit, false);
return;
}

info(`Using lock file: ${lockFile.path}`);
const cacheCwd = getCacheDirectoryCwd(lockFile.path);
info(`Resolving dependency cache directory in: ${cacheCwd}`);

// Get cache directories based on lock file type
const cachePaths = await getCacheDirectories(lockFile.type);
const cachePaths = await getCacheDirectories(lockFile.type, cacheCwd);
Comment on lines 21 to +26
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI enforces that compiled dist/ artifacts are committed and up-to-date after source changes (see .github/workflows/test.yml step “Verify dist is up to date”). This PR updates src/ behavior, so make sure to run vp run build and include any resulting dist/ changes in the PR to avoid CI failures.

Copilot uses AI. Check for mistakes.
if (!cachePaths.length) {
warning("No cache directories found. Skipping cache restore.");
warning(
`No cache directories found for ${lockFile.type} in ${cacheCwd}. Skipping cache restore.`,
);
setOutput(Outputs.CacheHit, false);
return;
}
Expand Down
60 changes: 59 additions & 1 deletion src/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
import { describe, it, expect, beforeEach, afterEach, vi } from "vite-plus/test";
import { existsSync, readdirSync } from "node:fs";
import { join } from "node:path";
import { detectLockFile } from "./utils.js";
import { getExecOutput } from "@actions/exec";
import { detectLockFile, getCacheDirectoryCwd, getCacheDirectories } from "./utils.js";
import { LockFileType } from "./types.js";

vi.mock("@actions/core", () => ({
info: vi.fn(),
warning: vi.fn(),
debug: vi.fn(),
}));

vi.mock("@actions/exec", () => ({
getExecOutput: vi.fn(),
}));

// Mock fs module
vi.mock("node:fs", async () => {
const actual = await vi.importActual<typeof import("node:fs")>("node:fs");
Expand Down Expand Up @@ -158,3 +169,50 @@ describe("detectLockFile", () => {
});
});
});

describe("getCacheDirectoryCwd", () => {
const mockWorkspace = "/test/workspace";

beforeEach(() => {
vi.stubEnv("GITHUB_WORKSPACE", mockWorkspace);
});

afterEach(() => {
vi.unstubAllEnvs();
});

it("should resolve cache cwd from relative lock file path", () => {
expect(getCacheDirectoryCwd("web/pnpm-lock.yaml")).toBe("/test/workspace/web");
});

it("should resolve cache cwd from absolute lock file path", () => {
expect(getCacheDirectoryCwd("/custom/path/pnpm-lock.yaml")).toBe("/custom/path");
});
});

describe("getCacheDirectories", () => {
afterEach(() => {
vi.resetAllMocks();
});

it("should run vp pm cache dir in the provided cwd", async () => {
vi.mocked(getExecOutput).mockResolvedValue({
exitCode: 0,
stdout: "/tmp/pnpm-store\n",
stderr: "",
});

const result = await getCacheDirectories(LockFileType.Pnpm, "/test/workspace/web");

expect(result).toEqual(["/tmp/pnpm-store"]);
expect(getExecOutput).toHaveBeenCalledWith(
"vp",
["pm", "cache", "dir"],
expect.objectContaining({
cwd: "/test/workspace/web",
silent: true,
ignoreReturnCode: true,
}),
);
});
});
21 changes: 15 additions & 6 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { info, warning, debug } from "@actions/core";
import { getExecOutput } from "@actions/exec";
import { existsSync, readdirSync } from "node:fs";
import { homedir } from "node:os";
import { isAbsolute, join, basename } from "node:path";
import { isAbsolute, join, basename, dirname } from "node:path";
import { LockFileType } from "./types.js";
import type { LockFileInfo } from "./types.js";

Expand All @@ -19,6 +19,10 @@ export function resolveWorkspacePath(filePath: string): string {
return isAbsolute(filePath) ? filePath : join(getWorkspaceDir(), filePath);
}

export function getCacheDirectoryCwd(lockFilePath: string): string {
return dirname(resolveWorkspacePath(lockFilePath));
}

// Lock file patterns in priority order
const LOCK_FILES: Array<{ filename: string; type: LockFileType }> = [
{ filename: "pnpm-lock.yaml", type: LockFileType.Pnpm },
Expand Down Expand Up @@ -86,21 +90,26 @@ function inferLockFileType(fullPath: string, filename: string): LockFileInfo {
/**
* Get cache directories based on package manager type
*/
export async function getCacheDirectories(lockType: LockFileType): Promise<string[]> {
export async function getCacheDirectories(lockType: LockFileType, cwd: string): Promise<string[]> {
switch (lockType) {
case LockFileType.Npm:
case LockFileType.Pnpm:
case LockFileType.Yarn:
return getViteCacheDir();
return getViteCacheDir(cwd);
default:
return [];
}
}

async function getCommandOutput(command: string, args: string[]): Promise<string | undefined> {
async function getCommandOutput(
command: string,
args: string[],
options?: { cwd?: string },
): Promise<string | undefined> {
const cmdStr = `${command} ${args.join(" ")}`;
try {
const result = await getExecOutput(command, args, {
cwd: options?.cwd,
silent: true,
ignoreReturnCode: true,
});
Expand All @@ -115,7 +124,7 @@ async function getCommandOutput(command: string, args: string[]): Promise<string
}
}

async function getViteCacheDir(): Promise<string[]> {
const cacheDir = await getCommandOutput("vp", ["pm", "cache", "dir"]);
async function getViteCacheDir(cwd: string): Promise<string[]> {
const cacheDir = await getCommandOutput("vp", ["pm", "cache", "dir"], { cwd });
return cacheDir ? [cacheDir] : [];
}
Loading