Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
68 changes: 34 additions & 34 deletions dist/index.mjs

Large diffs are not rendered by default.

189 changes: 189 additions & 0 deletions src/cache-vp.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import { describe, it, expect, beforeEach, afterEach, vi } from "vite-plus/test";
import { arch } from "node:os";
import { resolveVersion, restoreVpCache, saveVpCache } from "./cache-vp.js";
import { State } from "./types.js";
import { restoreCache, saveCache } from "@actions/cache";
import { saveState, getState, warning } from "@actions/core";

// Mock @actions/cache
vi.mock("@actions/cache", () => ({
restoreCache: vi.fn(),
saveCache: vi.fn(),
}));

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

describe("resolveVersion", () => {
afterEach(() => {
vi.restoreAllMocks();
});

it("should return explicit version as-is", async () => {
const result = await resolveVersion("0.1.8");
expect(result).toBe("0.1.8");
});

it("should return explicit semver-like versions as-is", async () => {
const result = await resolveVersion("1.0.0-beta.1");
expect(result).toBe("1.0.0-beta.1");
});

it("should resolve 'latest' from npm registry", async () => {
const fetchSpy = vi
.spyOn(globalThis, "fetch")
.mockResolvedValue(new Response(JSON.stringify({ version: "0.2.0" }), { status: 200 }));

const result = await resolveVersion("latest");
expect(result).toBe("0.2.0");
expect(fetchSpy).toHaveBeenCalledWith(
"https://registry.npmjs.org/vite-plus/latest",
expect.objectContaining({ signal: expect.any(AbortSignal) }),
);
});

it("should return undefined when fetch fails", async () => {
vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("network error"));

const result = await resolveVersion("latest");
expect(result).toBeUndefined();
});

it("should return undefined when fetch returns non-ok status", async () => {
vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response("Not Found", { status: 404 }));

const result = await resolveVersion("latest");
expect(result).toBeUndefined();
});

it("should return undefined for empty string input", async () => {
vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("should not be called"));

const result = await resolveVersion("");
expect(result).toBeUndefined();
Comment thread
fengmk2 marked this conversation as resolved.
Outdated
});
});

describe("restoreVpCache", () => {
beforeEach(() => {
vi.stubEnv("RUNNER_OS", "Linux");
vi.stubEnv("HOME", "/home/runner");
});

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

it("should return true on cache hit", async () => {
vi.mocked(restoreCache).mockResolvedValue(`setup-vp-Linux-${arch()}-0.1.8`);

const result = await restoreVpCache("0.1.8");

expect(result).toBe(true);
expect(saveState).toHaveBeenCalledWith(
State.VpCachePrimaryKey,
`setup-vp-Linux-${arch()}-0.1.8`,
);
expect(saveState).toHaveBeenCalledWith(
State.VpCacheMatchedKey,
`setup-vp-Linux-${arch()}-0.1.8`,
);
});

it("should return false on cache miss", async () => {
vi.mocked(restoreCache).mockResolvedValue(undefined);

const result = await restoreVpCache("0.1.8");

expect(result).toBe(false);
expect(saveState).toHaveBeenCalledWith(
State.VpCachePrimaryKey,
`setup-vp-Linux-${arch()}-0.1.8`,
);
});

it("should return false and warn on cache restore error", async () => {
vi.mocked(restoreCache).mockRejectedValue(new Error("cache error"));

const result = await restoreVpCache("0.1.8");

expect(result).toBe(false);
expect(warning).toHaveBeenCalled();
});

it("should use correct cache path", async () => {
vi.mocked(restoreCache).mockResolvedValue(undefined);

await restoreVpCache("0.1.8");

expect(restoreCache).toHaveBeenCalledWith(
["/home/runner/.vite-plus"],
`setup-vp-Linux-${arch()}-0.1.8`,
);
});
});

describe("saveVpCache", () => {
beforeEach(() => {
vi.stubEnv("HOME", "/home/runner");
});

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

it("should skip when no primary key", async () => {
vi.mocked(getState).mockReturnValue("");

await saveVpCache();

expect(saveCache).not.toHaveBeenCalled();
});

it("should skip when primary key matches matched key", async () => {
vi.mocked(getState).mockImplementation((key: string) => {
if (key === State.VpCachePrimaryKey) return `setup-vp-Linux-${arch()}-0.1.8`;
if (key === State.VpCacheMatchedKey) return `setup-vp-Linux-${arch()}-0.1.8`;
return "";
});

await saveVpCache();

expect(saveCache).not.toHaveBeenCalled();
});

it("should save cache on cache miss", async () => {
vi.mocked(getState).mockImplementation((key: string) => {
if (key === State.VpCachePrimaryKey) return `setup-vp-Linux-${arch()}-0.1.8`;
return "";
});
vi.mocked(saveCache).mockResolvedValue(12345);

await saveVpCache();

expect(saveCache).toHaveBeenCalledWith(
["/home/runner/.vite-plus"],
`setup-vp-Linux-${arch()}-0.1.8`,
);
});

it("should handle save errors gracefully", async () => {
vi.mocked(getState).mockImplementation((key: string) => {
if (key === State.VpCachePrimaryKey) return `setup-vp-Linux-${arch()}-0.1.8`;
return "";
});
vi.mocked(saveCache).mockRejectedValue(new Error("ReserveCacheError"));

await saveVpCache();

expect(warning).toHaveBeenCalled();
});
});
79 changes: 79 additions & 0 deletions src/cache-vp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { restoreCache, saveCache } from "@actions/cache";
import { info, debug, saveState, getState, warning } from "@actions/core";
import { arch, platform } from "node:os";
import { State } from "./types.js";
import { getVitePlusHome } from "./utils.js";

/**
* Resolve "latest" to a specific version number via npm registry.
* Returns undefined on failure so the caller can fall back to installing without cache.
*/
export async function resolveVersion(versionInput: string): Promise<string | undefined> {
if (versionInput && versionInput !== "latest") {
return versionInput;
}

try {
const response = await fetch("https://registry.npmjs.org/vite-plus/latest", {
Comment thread
fengmk2 marked this conversation as resolved.
Outdated
signal: AbortSignal.timeout(10_000),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = (await response.json()) as { version: string };
info(`Resolved latest vp version: ${data.version}`);
return data.version;
} catch (error) {
warning(`Failed to resolve latest vp version: ${error}. Skipping vp cache.`);
return undefined;
}
}

export async function restoreVpCache(version: string): Promise<boolean> {
const vpHome = getVitePlusHome();
const runnerOS = process.env.RUNNER_OS || platform();
const runnerArch = arch();
const primaryKey = `setup-vp-${runnerOS}-${runnerArch}-${version}`;

Comment thread
fengmk2 marked this conversation as resolved.
Comment on lines +34 to +39
debug(`Vp cache key: ${primaryKey}`);
debug(`Vp cache path: ${vpHome}`);
saveState(State.VpCachePrimaryKey, primaryKey);
Comment thread
fengmk2 marked this conversation as resolved.
Outdated

try {
const matchedKey = await restoreCache([vpHome], primaryKey);
if (matchedKey) {
info(`Vite+ restored from cache (key: ${matchedKey})`);
saveState(State.VpCacheMatchedKey, matchedKey);
return true;
}
} catch (error) {
warning(`Failed to restore vp cache: ${error}`);
}

return false;
}

export async function saveVpCache(): Promise<void> {
const primaryKey = getState(State.VpCachePrimaryKey);
const matchedKey = getState(State.VpCacheMatchedKey);

if (!primaryKey) {
debug("No vp cache key found. Skipping save.");
return;
}

if (primaryKey === matchedKey) {
info(`Vp cache hit on primary key "${primaryKey}". Skipping save.`);
return;
}

try {
const vpHome = getVitePlusHome();
const cacheId = await saveCache([vpHome], primaryKey);
if (cacheId === -1) {
warning("Vp cache save failed or was skipped.");
return;
}
info(`Vp cache saved with key: ${primaryKey}`);
} catch (error) {
warning(`Failed to save vp cache: ${String(error)}`);
}
}
6 changes: 5 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { installVitePlus } from "./install-viteplus.js";
import { runViteInstall } from "./run-install.js";
import { restoreCache } from "./cache-restore.js";
import { saveCache } from "./cache-save.js";
import { saveVpCache } from "./cache-vp.js";
import { State, Outputs } from "./types.js";
Comment on lines 5 to 9
import type { Inputs } from "./types.js";
import { resolveNodeVersionFile } from "./node-version-file.js";
Expand Down Expand Up @@ -59,7 +60,10 @@ async function printViteVersion(): Promise<void> {
}

async function runPost(inputs: Inputs): Promise<void> {
// Save cache if enabled
// Save vp binary cache (always)
await saveVpCache();

// Save dependency cache if enabled
if (inputs.cache) {
await saveCache();
}
Expand Down
34 changes: 22 additions & 12 deletions src/install-viteplus.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,33 @@
import { info, debug, addPath } from "@actions/core";
import { info, addPath } from "@actions/core";
import { exec } from "@actions/exec";
import { join } from "node:path";
import type { Inputs } from "./types.js";
import { DISPLAY_NAME } from "./types.js";
import { resolveVersion, restoreVpCache } from "./cache-vp.js";
import { getVitePlusHome } from "./utils.js";

const INSTALL_URL_SH = "https://staging.viteplus.dev/install.sh";
const INSTALL_URL_PS1 = "https://staging.viteplus.dev/install.ps1";
const INSTALL_URL_SH = "https://viteplus.dev/install.sh";
const INSTALL_URL_PS1 = "https://viteplus.dev/install.ps1";

export async function installVitePlus(inputs: Inputs): Promise<void> {
const { version } = inputs;
info(`Installing ${DISPLAY_NAME}@${version}...`);

const env = { ...process.env, VITE_PLUS_VERSION: version };
// Try to resolve version and restore from cache
const resolvedVersion = await resolveVersion(version);
if (resolvedVersion) {
const cacheHit = await restoreVpCache(resolvedVersion);
if (cacheHit) {
ensureVitePlusBinInPath();
info(`${DISPLAY_NAME} restored from cache`);
return;
}
}

// Cache miss or resolution failed — install fresh
const installVersion = resolvedVersion || version;
info(`Installing ${DISPLAY_NAME}@${installVersion}...`);

const env = { ...process.env, VITE_PLUS_VERSION: installVersion };
let exitCode: number;

if (process.platform === "win32") {
Expand All @@ -32,14 +48,8 @@ export async function installVitePlus(inputs: Inputs): Promise<void> {
}

function ensureVitePlusBinInPath(): void {
const home = process.platform === "win32" ? process.env.USERPROFILE : process.env.HOME;
if (!home) {
debug("Could not determine home directory");
return;
}
const binDir = join(home, ".vite-plus", "bin");
const binDir = join(getVitePlusHome(), "bin");
Comment thread
fengmk2 marked this conversation as resolved.
if (!process.env.PATH?.includes(binDir)) {
addPath(binDir);
debug(`Added ${binDir} to PATH`);
}
}
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export enum State {
CacheMatchedKey = "CACHE_MATCHED_KEY",
CachePaths = "CACHE_PATHS",
InstalledVersion = "INSTALLED_VERSION",
VpCachePrimaryKey = "VP_CACHE_PRIMARY_KEY",
VpCacheMatchedKey = "VP_CACHE_MATCHED_KEY",
}

// Output keys
Expand Down
6 changes: 6 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ import { isAbsolute, join, basename } from "node:path";
import { LockFileType } from "./types.js";
import type { LockFileInfo } from "./types.js";

export function getVitePlusHome(): string {
const home = process.platform === "win32" ? process.env.USERPROFILE : process.env.HOME;
if (!home) throw new Error("Could not determine home directory");
return join(home, ".vite-plus");
Comment thread
fengmk2 marked this conversation as resolved.
Outdated
}

export function getWorkspaceDir(): string {
return process.env.GITHUB_WORKSPACE || process.cwd();
}
Expand Down
Loading