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
58 changes: 29 additions & 29 deletions dist/index.mjs

Large diffs are not rendered by default.

10 changes: 7 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { State, Outputs } from "./types.js";
import type { Inputs } from "./types.js";
import { resolveNodeVersionFile } from "./node-version-file.js";
import { configAuthentication, propagateProjectNpmrcAuth } from "./auth.js";
import { getConfiguredProjectDir } from "./utils.js";
import { getConfiguredProjectDir, EMPTY_STDIN } from "./utils.js";

async function runMain(inputs: Inputs): Promise<void> {
// Mark that post action should run
Expand All @@ -29,7 +29,7 @@ async function runMain(inputs: Inputs): Promise<void> {
// Step 3: Set up Node.js version if specified
if (nodeVersion) {
info(`Setting up Node.js ${nodeVersion} via vp env use...`);
await exec("vp", ["env", "use", nodeVersion]);
await exec("vp", ["env", "use", nodeVersion], { input: EMPTY_STDIN });
}

// Step 4: Configure registry authentication
Expand Down Expand Up @@ -61,7 +61,11 @@ async function runMain(inputs: Inputs): Promise<void> {

async function printViteVersion(cwd: string): Promise<void> {
try {
const result = await getExecOutput("vp", ["--version"], { cwd, silent: true });
const result = await getExecOutput("vp", ["--version"], {
cwd,
silent: true,
input: EMPTY_STDIN,
});
const versionOutput = result.stdout.trim();
info(versionOutput);

Expand Down
68 changes: 68 additions & 0 deletions src/run-install.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { describe, it, expect, afterEach, beforeEach, vi } from "vite-plus/test";
import { getExecOutput } from "@actions/exec";
import { runViteInstall } from "./run-install.js";
import type { Inputs } from "./types.js";

vi.mock("@actions/core", () => ({
startGroup: vi.fn(),
endGroup: vi.fn(),
setFailed: vi.fn(),
info: vi.fn(),
error: vi.fn(),
}));

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

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

describe("runViteInstall", () => {
beforeEach(() => {
process.env.GITHUB_WORKSPACE = "/tmp/workspace";
vi.mocked(getExecOutput).mockResolvedValue({ exitCode: 0, stdout: "", stderr: "" });
});

afterEach(() => {
vi.resetAllMocks();
delete process.env.GITHUB_WORKSPACE;
});

// Regression test for https://github.com/voidzero-dev/setup-vp/issues/90
//
// On Windows runners stdin is not a TTY. Vite+ routes the package-manager
// `.cmd` shims through PowerShell, whose `.ps1` wrappers read stdin and block
// forever when the child's stdin pipe is left open and empty, hanging the job
// for hours. `@actions/exec` only closes the child's stdin when `input` is a
// truthy value, so we must pass an empty Buffer (EOF) to avoid the hang.
it("closes stdin so vp install cannot hang on Windows runners", async () => {
await runViteInstall(baseInputs);

expect(getExecOutput).toHaveBeenCalledTimes(1);
const options = vi.mocked(getExecOutput).mock.calls[0][2];
expect(options?.input).toBeDefined();
expect(Buffer.isBuffer(options?.input)).toBe(true);
expect(options?.input?.length).toBe(0);
});

it("closes stdin for the sfw-wrapped install too", async () => {
await runViteInstall({ ...baseInputs, sfw: true });

const [cmd, args, options] = vi.mocked(getExecOutput).mock.calls[0];
expect(cmd).toBe("sfw");
expect(args).toEqual(["vp", "install"]);
expect(Buffer.isBuffer(options?.input)).toBe(true);
expect(options?.input?.length).toBe(0);
});
});
5 changes: 4 additions & 1 deletion src/run-install.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { startGroup, endGroup, setFailed, info, error as logError } from "@actions/core";
import { getExecOutput } from "@actions/exec";
import type { Inputs } from "./types.js";
import { getConfiguredProjectDir, getInstallCwd } from "./utils.js";
import { getConfiguredProjectDir, getInstallCwd, EMPTY_STDIN } from "./utils.js";

const MAX_ERROR_TAIL = 4000;

Expand Down Expand Up @@ -31,6 +31,9 @@ export async function runViteInstall(inputs: Inputs): Promise<void> {
const { exitCode, stdout, stderr } = await getExecOutput(cmd, args, {
cwd,
ignoreReturnCode: true,
// Close stdin (EOF) so the package-manager PowerShell wrappers Vite+
// spawns on Windows do not block on an open stdin pipe. See EMPTY_STDIN.
input: EMPTY_STDIN,
});
endGroup();

Expand Down
17 changes: 17 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,23 @@ import type { Inputs } from "./types.js";
import { LockFileType } from "./types.js";
import type { LockFileInfo } from "./types.js";

/**
* Empty stdin payload (EOF) for child processes we spawn.
*
* On Windows runners stdin is not a TTY. Vite+ routes the package-manager
* `.cmd` shims (pnpm/npm/yarn) through PowerShell, whose `.ps1` wrappers
* introspect stdin (`$MyInvocation.ExpectingInput`) and block forever when the
* child's stdin pipe is left open and empty, hanging the job for hours until
* GitHub cancels it. `@actions/exec` leaves the child's stdin open unless
* `input` is set, so we pass an empty buffer to send EOF immediately.
*
* A `Buffer` (not `""`, which is falsy) is required: `@actions/exec` only calls
* `cp.stdin.end(input)` when `input` is truthy.
*
* See https://github.com/voidzero-dev/setup-vp/issues/90
*/
export const EMPTY_STDIN = Buffer.alloc(0);

export function getVitePlusHome(): string {
const home = process.platform === "win32" ? process.env.USERPROFILE : process.env.HOME;
return join(home || homedir(), ".vite-plus");
Expand Down
Loading