Skip to content
Open
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
36 changes: 36 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,42 @@ jobs:
echo "Installed version: ${{ steps.setup.outputs.version }}"
echo "Cache hit: ${{ steps.setup.outputs.cache-hit }}"

test-task-cache:
strategy:
fail-fast: false
matrix:
include:
- bin: npm
lockfile: package-lock.json
- bin: yarn
lockfile: yarn.lock
- bin: pnpm
lockfile: pnpm-lock.yaml
- bin: bun
lockfile: bun.lock
runs-on: ubuntu-latest
steps:
- uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2

- name: Create test project
run: |
mkdir -p test-project
cd test-project
echo '{"name":"test-project","private":true}' > package.json
touch ${{ matrix.lockfile }}

- name: Setup Vite+ with ${{ matrix.bin }}
uses: ./
id: setup
with:
run-install: true
task-cache: true

- name: Verify cache & path existence
run: |
echo "Task cache hit: ${{ steps.setup.outputs.task-cache-hit }}"
ls "./node_modules/.vite/task-cache"

test-vp-exec:
strategy:
fail-fast: false
Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ GitHub Action, GitLab CI/CD remote template, and Azure Pipelines step template t
- Install Vite+ globally via official install scripts
- GitHub Action: optionally set up a specific Node.js version via `vp env use`
- GitHub Action: cache project dependencies with auto-detection of lock files
- GitHub Action: save task caches to GitHub Actions Cache
- Optionally run `vp install` after setup
- Optionally wrap `vp install` with [Socket Firewall Free (`sfw`)](https://docs.socket.dev/docs/socket-firewall-free) to block malicious dependencies
- Support for all major package managers (npm, pnpm, yarn, bun)
Expand Down Expand Up @@ -98,6 +99,21 @@ steps:
run-install: true
```

### With Task Caching

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure what I'm supposed to test here, is it just trying to save and restore using the setup-vp action vs the regualr cache action?


This will save the Vite+ task cache to GitHub Actions with a specific key. This only saves the task cache; other caches (from Vite builds, for example) that happen to be included in the `node_modules/.vite` folder is not included.

At the end, the cache is written back to GitHub.

```yaml
steps:
- uses: actions/checkout@v6
- uses: voidzero-dev/setup-vp@v1.17.0
with:
cache: true
task-cache: true
```

### Specific Version

```yaml
Expand Down
6 changes: 6 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ inputs:
description: "Enable caching of project dependencies"
required: false
default: "false"
task-cache:
description: "Enable task caching via GitHub Actions"
required: false
default: "false"
Comment on lines +40 to +43

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Document the new task-cache interface

The new input and output are absent from the public interface documentation: the Inputs table at README.md:329-344, Outputs table at README.md:350-355, and Caching section at README.md:357-375 still describe only dependency caching. Since the repository's marketplace README is how users discover and configure this action, the feature is effectively hidden and users cannot learn its cache path/key behavior or consume task-cache-hit; add the new input, output, and usage details there.

Useful? React with 👍 / 👎.

cache-dependency-path:
description: "Path to lock file for cache key generation. Auto-detected if not specified."
required: false
Expand All @@ -52,6 +56,8 @@ outputs:
description: "The installed version of Vite+"
cache-hit:
description: "Boolean indicating if cache was restored"
task-cache-hit:
description: "Boolean indicating if task cache was restored"

runs:
using: node24
Expand Down
4 changes: 2 additions & 2 deletions dist/azure/index.mjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/gitlab/index.mjs

Large diffs are not rendered by default.

130 changes: 65 additions & 65 deletions dist/index.mjs

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { setupSfw } from "./install-sfw.js";
import { runViteInstall } from "./run-install.js";
import { restoreCache } from "./cache-restore.js";
import { saveCache } from "./cache-save.js";
import { restoreTaskCache } from "./task-cache-restore.js";
import { saveTaskCache } from "./task-cache-save.js";
import { State, Outputs } from "./types.js";
import type { Inputs } from "./types.js";
import { resolveNodeVersionFile } from "./node-version-file.js";
Expand Down Expand Up @@ -53,6 +55,11 @@ async function runMain(inputs: Inputs): Promise<void> {
await restoreCache(inputs);
}

// Step 5.5: Restore task cache if enabled
if (inputs.taskCache) {
await restoreTaskCache(inputs);
}

// Step 6: Install Socket Firewall Free if requested (must run before vp install).
// setupSfw centralizes all the decision branches: run-install disabled, sfw
// already on PATH (e.g. via socketdev/action@<sha>), supported platform
Expand Down Expand Up @@ -88,6 +95,9 @@ async function runPost(inputs: Inputs): Promise<void> {
if (inputs.cache) {
await saveCache();
}
if (inputs.taskCache) {
await saveTaskCache();
}
}

async function main(): Promise<void> {
Expand Down
1 change: 1 addition & 0 deletions src/inputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export function getInputs(): Inputs {
sfw: getBooleanInput("sfw"),
cache: getBooleanInput("cache"),
cacheDependencyPath: getInput("cache-dependency-path") || undefined,
taskCache: getBooleanInput("task-cache"),
registryUrl: getInput("registry-url") || undefined,
scope: getInput("scope") || undefined,
};
Expand Down
1 change: 1 addition & 0 deletions src/install-sfw.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ function makeInputs(overrides: Partial<Inputs> = {}): Inputs {
sfw: true,
cache: false,
cacheDependencyPath: undefined,
taskCache: false,
registryUrl: undefined,
scope: undefined,
...overrides,
Expand Down
1 change: 1 addition & 0 deletions src/install-viteplus.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const baseInputs: Inputs = {
sfw: false,
cache: false,
cacheDependencyPath: undefined,
taskCache: false,
registryUrl: undefined,
scope: undefined,
};
Expand Down
1 change: 1 addition & 0 deletions src/run-install.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const baseInputs: Inputs = {
runInstall: [{}],
sfw: true,
cache: false,
taskCache: false,
};

const mockedExec = vi.mocked(getExecOutput);
Expand Down
191 changes: 191 additions & 0 deletions src/task-cache-restore.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import { describe, it, expect, beforeEach, afterEach, vi } from "vite-plus/test";

// Mock external dependencies before importing the module
vi.mock("@actions/cache", () => ({
restoreCache: vi.fn(),
}));
vi.mock("@actions/core", () => ({
warning: vi.fn(),
info: vi.fn(),
debug: vi.fn(),
saveState: vi.fn(),
setOutput: vi.fn(),
}));
vi.mock("./utils.js", () => ({
getConfiguredProjectDir: vi.fn(() => "/workspace"),
}));

import { restoreCache as restoreCacheAction } from "@actions/cache";
import { warning, info, saveState, setOutput } from "@actions/core";
import { restoreTaskCache } from "./task-cache-restore.js";
import { State, Outputs } from "./types.js";
import type { Inputs } from "./types.js";

const mockedRestoreCacheAction = vi.mocked(restoreCacheAction);
const mockedWarning = vi.mocked(warning);
const mockedInfo = vi.mocked(info);
const mockedSaveState = vi.mocked(saveState);
const mockedSetOutput = vi.mocked(setOutput);

const baseInputs: Inputs = {
version: "latest",
nodeVersion: undefined,
nodeVersionFile: undefined,
workingDirectory: undefined,
runInstall: [],
sfw: false,
cache: false,
cacheDependencyPath: undefined,
taskCache: true,
registryUrl: undefined,
scope: undefined,
};

describe("restoreTaskCache", () => {
const originalEnv = process.env;

beforeEach(() => {
vi.clearAllMocks();
process.env = { ...originalEnv };
});

afterEach(() => {
process.env = originalEnv;
});

it("restores task cache with correct key pattern", async () => {
process.env.RUNNER_OS = "Linux";
process.env.GITHUB_RUN_ID = "12345";
process.env.GITHUB_RUN_ATTEMPT = "1";

mockedRestoreCacheAction.mockResolvedValue("vite-task-Linux-x64-12345-1");

await restoreTaskCache(baseInputs);

expect(mockedRestoreCacheAction).toHaveBeenCalledWith(
expect.arrayContaining([expect.stringContaining("node_modules")]),
"vite-task-Linux-x64-12345-1",
["vite-task-Linux-x64-"],
);

expect(mockedSaveState).toHaveBeenCalledWith(
State.TaskCachePrimaryKey,
"vite-task-Linux-x64-12345-1",
);
expect(mockedSaveState).toHaveBeenCalledWith(
State.TaskCacheMatchedKey,
"vite-task-Linux-x64-12345-1",
);
expect(mockedSetOutput).toHaveBeenCalledWith(Outputs.TaskCacheHit, true);
expect(mockedInfo).toHaveBeenCalledWith(expect.stringContaining("Task cache restored"));
});

it("sets cache-hit to false when cache is not found", async () => {
process.env.RUNNER_OS = "Linux";
process.env.GITHUB_RUN_ID = "12345";
process.env.GITHUB_RUN_ATTEMPT = "1";

mockedRestoreCacheAction.mockResolvedValue(undefined);

await restoreTaskCache(baseInputs);

expect(mockedSetOutput).toHaveBeenCalledWith(Outputs.TaskCacheHit, false);
expect(mockedInfo).toHaveBeenCalledWith("Task cache not found");
});

it("warns and skips when GITHUB_RUN_ID is missing", async () => {
process.env.RUNNER_OS = "Linux";
process.env.GITHUB_RUN_ATTEMPT = "1";
delete process.env.GITHUB_RUN_ID;

await restoreTaskCache(baseInputs);

expect(mockedWarning).toHaveBeenCalledWith(
expect.stringContaining("GitHub run ID or attempt not found"),
);
expect(mockedSetOutput).toHaveBeenCalledWith(Outputs.TaskCacheHit, false);
expect(mockedRestoreCacheAction).not.toHaveBeenCalled();
});

it("warns and skips when GITHUB_RUN_ATTEMPT is missing", async () => {
process.env.RUNNER_OS = "Linux";
process.env.GITHUB_RUN_ID = "12345";
delete process.env.GITHUB_RUN_ATTEMPT;

await restoreTaskCache(baseInputs);

expect(mockedWarning).toHaveBeenCalledWith(
expect.stringContaining("GitHub run ID or attempt not found"),
);
expect(mockedSetOutput).toHaveBeenCalledWith(Outputs.TaskCacheHit, false);
expect(mockedRestoreCacheAction).not.toHaveBeenCalled();
});

it("uses correct cache path relative to project directory", async () => {
process.env.RUNNER_OS = "Windows";
process.env.GITHUB_RUN_ID = "67890";
process.env.GITHUB_RUN_ATTEMPT = "2";

mockedRestoreCacheAction.mockResolvedValue(undefined);

await restoreTaskCache(baseInputs);

const cachePathsCall = mockedSaveState.mock.calls.find(([key]) => key === State.TaskCachePaths);
expect(cachePathsCall).toBeDefined();
if (cachePathsCall) {
const paths = JSON.parse(cachePathsCall[1] as string);
expect(paths[0]).toMatch(/node_modules/);
expect(paths[0]).toMatch(/task-cache/);
}
});

it("handles different OS and architecture combinations", async () => {
process.env.RUNNER_OS = "macOS";
process.env.GITHUB_RUN_ID = "11111";
process.env.GITHUB_RUN_ATTEMPT = "3";

// Mock process.arch
const originalArch = process.arch;
Object.defineProperty(process, "arch", {
value: "arm64",
configurable: true,
});

mockedRestoreCacheAction.mockResolvedValue(undefined);

await restoreTaskCache(baseInputs);

expect(mockedSaveState).toHaveBeenCalledWith(
State.TaskCachePrimaryKey,
"vite-task-macOS-arm64-11111-3",
);

// Restore original arch
Object.defineProperty(process, "arch", {
value: originalArch,
configurable: true,
});
});

it("saves cache paths as JSON string", async () => {
process.env.RUNNER_OS = "Linux";
process.env.GITHUB_RUN_ID = "12345";
process.env.GITHUB_RUN_ATTEMPT = "1";

mockedRestoreCacheAction.mockResolvedValue(undefined);

await restoreTaskCache(baseInputs);

expect(mockedSaveState).toHaveBeenCalledWith(
State.TaskCachePaths,
expect.stringMatching(/^\[.*\]$/),
);

const cachePathsCall = mockedSaveState.mock.calls.find(([key]) => key === State.TaskCachePaths);
if (cachePathsCall) {
const paths = JSON.parse(cachePathsCall[1] as string);
expect(Array.isArray(paths)).toBe(true);
expect(paths.length).toBeGreaterThan(0);
}
});
});
Loading