Skip to content

Commit 6e1631f

Browse files
committed
fix: require valid VpDirs output
1 parent 7d790c6 commit 6e1631f

9 files changed

Lines changed: 142 additions & 40 deletions

File tree

dist/azure/index.mjs

Lines changed: 2 additions & 2 deletions
Large diffs are not rendered by default.

dist/index.mjs

Lines changed: 2 additions & 2 deletions
Large diffs are not rendered by default.

gitlab/bootstrap.sh

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,31 @@ setup_vp_export_env() {
3737
printf "\n" >> "$SETUP_VP_ENV_FILE"
3838
}
3939

40+
setup_vp_read_bin_dir() {
41+
awk '
42+
{
43+
separator = index($0, "\t")
44+
if (separator == 0) next
45+
key = substr($0, 1, separator - 1)
46+
value = substr($0, separator + 1)
47+
sub(/^[[:space:]]+/, "", value)
48+
sub(/[[:space:]]+$/, "", value)
49+
if (key == "data") data = value
50+
if (key == "bin") bin = value
51+
if (key == "cache") cache = value
52+
if (key == "config") config = value
53+
if (key == "state") state = value
54+
}
55+
END {
56+
if (data != "" && bin != "" && cache != "" && config != "" && state != "") {
57+
print bin
58+
exit 0
59+
}
60+
exit 1
61+
}
62+
' "$1"
63+
}
64+
4065
setup_vp_install_viteplus_from() {
4166
setup_vp_url="$1"
4267
setup_vp_download "$setup_vp_url" "$setup_vp_install_tmp" || return 1
@@ -162,12 +187,13 @@ setup_vp_runtime_tmp="$(mktemp "${TMPDIR:-/tmp}/setup-vp-gitlab-runtime.XXXXXX.m
162187
trap 'rm -f "$setup_vp_install_tmp" "$setup_vp_dirs_tmp" "$setup_vp_runtime_tmp"' EXIT
163188

164189
setup_vp_install_viteplus
165-
setup_vp_bin_dir=""
166190
if [ "$setup_vp_detect_dirs" = "true" ]; then
167-
setup_vp_bin_dir="$(awk -F '\t' '$1 == "bin" { print $2; exit }' "$setup_vp_dirs_tmp")"
168-
fi
169-
if [ -z "$setup_vp_bin_dir" ]; then
170-
# Vite+ releases before VpDirs always use the monolithic layout.
191+
if ! setup_vp_bin_dir="$(setup_vp_read_bin_dir "$setup_vp_dirs_tmp")"; then
192+
echo "setup-vp: Vite+ was installed successfully, but setup-vp could not resolve its VpDirs." >&2
193+
return 1 2>/dev/null || exit 1
194+
fi
195+
else
196+
# Vite+ releases before VpDirs use the monolithic layout.
171197
setup_vp_bin_dir="$HOME/.vite-plus/bin"
172198
fi
173199
export PATH="$setup_vp_bin_dir:$PATH"

src/azure/install-viteplus.test.ts

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,9 @@ describe("installVitePlus", () => {
1919
it("uses PowerShell installers on Windows and bash installers on Unix", async () => {
2020
const calls: NodeJS.Platform[] = [];
2121
const runInstall = vi.fn(
22-
(
23-
_url: string,
24-
_env: Record<string, string>,
25-
platform: NodeJS.Platform = process.platform,
26-
) => {
22+
(_url: string, env: Record<string, string>, platform: NodeJS.Platform = process.platform) => {
2723
calls.push(platform);
24+
writeDirsFile(env, `/test/${platform}/bin`);
2825
return 0;
2926
},
3027
);
@@ -82,7 +79,10 @@ describe("installVitePlus", () => {
8279
});
8380

8481
it("routes pkg.pr.new commit builds through VP_PR_VERSION", async () => {
85-
const runInstall = vi.fn(() => 0);
82+
const runInstall = vi.fn((_url: string, env: Record<string, string>) => {
83+
writeDirsFile(env, "/test/data/bin");
84+
return 0;
85+
});
8686
const sha = "a".repeat(40);
8787

8888
await installVitePlus(`0.0.0-commit.${sha}`, {
@@ -102,6 +102,34 @@ describe("installVitePlus", () => {
102102
expect(installCalls[0]?.[1]?.SETUP_VP_DIRS_FILE).toMatch(/setup-vp-dirs-.*\.txt$/);
103103
});
104104

105+
it.each([
106+
{ version: "0.3.0", output: undefined },
107+
{ version: `0.0.0-commit.${"a".repeat(40)}`, output: "bin\t/test/data/bin\n" },
108+
{ version: "latest", output: "" },
109+
])("fails when $version does not report valid VpDirs", async ({ version, output }) => {
110+
const prependPath = vi.fn();
111+
const runInstall = vi.fn((_url: string, env: Record<string, string>) => {
112+
if (output !== undefined) writeFileSync(env.SETUP_VP_DIRS_FILE, output);
113+
return 0;
114+
});
115+
116+
await expect(
117+
installVitePlus(version, {
118+
platform: "linux",
119+
env: { PATH: "/usr/bin" },
120+
prependPath,
121+
sleep: async () => undefined,
122+
runInstall,
123+
logWarningFn: () => undefined,
124+
}),
125+
).rejects.toThrow(
126+
"Vite+ was installed successfully, but setup-vp could not resolve its VpDirs.",
127+
);
128+
129+
expect(runInstall).toHaveBeenCalledTimes(1);
130+
expect(prependPath).not.toHaveBeenCalled();
131+
});
132+
105133
it("prepends the bin directory reported by the installed payload", async () => {
106134
const prependPath = vi.fn();
107135
const env = { PATH: "/usr/bin" };

src/azure/install-viteplus.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import { getInstallScriptUrls, pkgPrNewCommitSha } from "../ci/install-script-ur
66
import {
77
createVitePlusDirsFile,
88
getInstallScriptCommand,
9-
readVitePlusDirs,
109
removeVitePlusDirsFile,
10+
resolveVitePlusBinDir,
1111
supportsVitePlusDirs,
1212
VP_DIRS_FILE_ENV,
1313
} from "../ci/vp-dirs.js";
@@ -120,10 +120,7 @@ export async function installVitePlus(
120120
};
121121

122122
const ensureBinInPath = (): void => {
123-
// Pre-VpDirs releases cannot emit a dump and always use this legacy path.
124-
const binDir =
125-
(dirsFile ? readVitePlusDirs(dirsFile)?.bin : undefined) ??
126-
join(getVitePlusHome(platform), "bin");
123+
const binDir = resolveVitePlusBinDir(dirsFile, join(getVitePlusHome(platform), "bin"));
127124
const separator = platform === "win32" ? ";" : ":";
128125
if (!targetEnv.PATH?.split(separator).includes(binDir)) {
129126
targetEnv.PATH = `${binDir}${separator}${targetEnv.PATH || ""}`;

src/ci/vp-dirs.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,16 @@ export function readVitePlusDirs(filePath: string): VitePlusDirs | undefined {
5050
}
5151
}
5252

53+
export function resolveVitePlusBinDir(dirsFile: string | undefined, legacyBinDir: string): string {
54+
if (!dirsFile) return legacyBinDir;
55+
56+
const dirs = readVitePlusDirs(dirsFile);
57+
if (!dirs) {
58+
throw new Error("Vite+ was installed successfully, but setup-vp could not resolve its VpDirs.");
59+
}
60+
return dirs.bin;
61+
}
62+
5363
export function parseVitePlusDirs(output: string): VitePlusDirs | undefined {
5464
const dirs = new Map<string, string>();
5565

src/gitlab/bootstrap.test.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,20 @@ describe("GitLab bootstrap", () => {
1717
it("uses the bin directory reported by the installed payload", () => {
1818
expect(bootstrap).toContain('export VP_VPDIRS_AWARE="1"');
1919
expect(bootstrap).toContain('VP_DUMP_DIRS=1 "$SHIM_DIR/vp"');
20-
expect(bootstrap).toContain("setup_vp_bin_dir=\"$(awk -F '\\t'");
20+
expect(bootstrap).toContain('setup_vp_bin_dir="$(setup_vp_read_bin_dir "$setup_vp_dirs_tmp")"');
2121
expect(bootstrap).toContain('export PATH="$setup_vp_bin_dir:$PATH"');
2222
});
2323

24+
it("fails when a VpDirs-aware install does not report valid directories", () => {
25+
for (const name of ["data", "bin", "cache", "config", "state"]) {
26+
expect(bootstrap).toContain(`if (key == "${name}") ${name} = value`);
27+
}
28+
expect(bootstrap).toContain(
29+
"Vite+ was installed successfully, but setup-vp could not resolve its VpDirs.",
30+
);
31+
expect(bootstrap).toContain("return 1 2>/dev/null || exit 1");
32+
});
33+
2434
it("limits VpDirs detection to supported versions", () => {
2535
expect(bootstrap).toContain('setup_vp_detect_dirs="true"');
2636
expect(bootstrap).toContain('[ "$setup_vp_minor" -lt 3 ]');

src/install-viteplus.test.ts

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ function writeDirsFile(options: unknown, bin: string): void {
4848
);
4949
}
5050

51+
function mockSuccessfulInstallOnce(): void {
52+
vi.mocked(exec).mockImplementationOnce(async (_command, _args, options) => {
53+
const env = (options as { env: Record<string, string> }).env;
54+
if (env.SETUP_VP_DIRS_FILE) writeDirsFile(options, "/test/data/bin");
55+
return 0;
56+
});
57+
}
58+
5159
describe("installVitePlus", () => {
5260
// installVitePlus spreads process.env into the child env, so a VP_PR_VERSION
5361
// inherited from the runner (setup-vp's own CI sets it) would make these tests
@@ -82,7 +90,7 @@ describe("installVitePlus", () => {
8290
vi.stubEnv("PATH", "/usr/bin");
8391
vi.stubEnv("VP_VPDIRS_AWARE", "1");
8492
vi.stubEnv("SETUP_VP_DIRS_FILE", "/tmp/stale-vp-dirs");
85-
vi.mocked(exec).mockResolvedValueOnce(0);
93+
mockSuccessfulInstallOnce();
8694

8795
await installVitePlus({ ...baseInputs, version: "0.2.9" });
8896

@@ -94,8 +102,30 @@ describe("installVitePlus", () => {
94102
expect((options as { env: Record<string, string> }).env.SETUP_VP_DIRS_FILE).toBeUndefined();
95103
});
96104

105+
it.each([
106+
{ version: "0.3.0", output: undefined },
107+
{ version: `0.0.0-commit.${commitSha}`, output: "bin\t/test/data/bin\n" },
108+
{ version: "latest", output: "" },
109+
])("should fail when $version does not report valid VpDirs", async ({ version, output }) => {
110+
vi.mocked(exec).mockImplementationOnce(async (_command, _args, options) => {
111+
if (output !== undefined) {
112+
const env = (options as { env: Record<string, string> }).env;
113+
writeFileSync(env.SETUP_VP_DIRS_FILE, output);
114+
}
115+
return 0;
116+
});
117+
118+
await expect(installVitePlus({ ...baseInputs, version })).rejects.toThrow(
119+
"Vite+ was installed successfully, but setup-vp could not resolve its VpDirs.",
120+
);
121+
122+
expect(exec).toHaveBeenCalledTimes(1);
123+
expect(addPath).not.toHaveBeenCalled();
124+
});
125+
97126
it("should retry on transient failure and eventually succeed", async () => {
98-
vi.mocked(exec).mockResolvedValueOnce(6).mockResolvedValueOnce(6).mockResolvedValueOnce(0);
127+
vi.mocked(exec).mockResolvedValueOnce(6).mockResolvedValueOnce(6);
128+
mockSuccessfulInstallOnce();
99129

100130
await installVitePlus(baseInputs);
101131

@@ -112,7 +142,8 @@ describe("installVitePlus", () => {
112142
});
113143

114144
it("should retry when exec itself throws (e.g. process spawn error)", async () => {
115-
vi.mocked(exec).mockRejectedValueOnce(new Error("spawn bash ENOENT")).mockResolvedValueOnce(0);
145+
vi.mocked(exec).mockRejectedValueOnce(new Error("spawn bash ENOENT"));
146+
mockSuccessfulInstallOnce();
116147

117148
await installVitePlus(baseInputs);
118149

@@ -121,7 +152,8 @@ describe("installVitePlus", () => {
121152
});
122153

123154
it("should fall back to the GitHub install URL after a single primary failure", async () => {
124-
vi.mocked(exec).mockResolvedValueOnce(35).mockResolvedValueOnce(0);
155+
vi.mocked(exec).mockResolvedValueOnce(35);
156+
mockSuccessfulInstallOnce();
125157

126158
await installVitePlus(baseInputs);
127159

@@ -157,7 +189,7 @@ describe("installVitePlus", () => {
157189
])(
158190
"should install $desc with the install script from their git ref",
159191
async ({ version, ref }) => {
160-
vi.mocked(exec).mockResolvedValueOnce(0);
192+
mockSuccessfulInstallOnce();
161193

162194
await installVitePlus({ ...baseInputs, version });
163195

@@ -169,7 +201,8 @@ describe("installVitePlus", () => {
169201
);
170202

171203
it("should fall back to the jsDelivr mirror of the pinned script on failure", async () => {
172-
vi.mocked(exec).mockResolvedValueOnce(35).mockResolvedValueOnce(0);
204+
vi.mocked(exec).mockResolvedValueOnce(35);
205+
mockSuccessfulInstallOnce();
173206

174207
await installVitePlus({ ...baseInputs, version: "0.2.9" });
175208

@@ -188,8 +221,8 @@ describe("installVitePlus", () => {
188221
.mockResolvedValueOnce(22)
189222
.mockResolvedValueOnce(22)
190223
.mockResolvedValueOnce(22)
191-
.mockResolvedValueOnce(22)
192-
.mockResolvedValueOnce(0);
224+
.mockResolvedValueOnce(22);
225+
mockSuccessfulInstallOnce();
193226

194227
await installVitePlus({ ...baseInputs, version: "0.2.9" });
195228

@@ -215,7 +248,7 @@ describe("installVitePlus", () => {
215248
});
216249

217250
it("should source the downloaded bash installer and dump its resolved directories", async () => {
218-
vi.mocked(exec).mockResolvedValueOnce(0);
251+
mockSuccessfulInstallOnce();
219252

220253
await installVitePlus(baseInputs);
221254

@@ -232,7 +265,7 @@ describe("installVitePlus", () => {
232265
});
233266

234267
it("should declare VpDirs support to the installer", async () => {
235-
vi.mocked(exec).mockResolvedValueOnce(0);
268+
mockSuccessfulInstallOnce();
236269

237270
await installVitePlus(baseInputs);
238271

@@ -259,7 +292,7 @@ describe("installVitePlus", () => {
259292
expected: undefined,
260293
},
261294
])("$desc", async ({ version, expected }) => {
262-
vi.mocked(exec).mockResolvedValueOnce(0);
295+
mockSuccessfulInstallOnce();
263296

264297
await installVitePlus({ ...baseInputs, version });
265298

@@ -284,7 +317,7 @@ describe("installVitePlus", () => {
284317
expected: undefined,
285318
},
286319
])("$desc", async ({ nodeManager, expected }) => {
287-
vi.mocked(exec).mockResolvedValueOnce(0);
320+
mockSuccessfulInstallOnce();
288321

289322
await installVitePlus({ ...baseInputs, nodeManager });
290323

src/install-viteplus.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import { getInstallScriptUrls, pkgPrNewCommitSha } from "./ci/install-script-url
66
import {
77
createVitePlusDirsFile,
88
getInstallScriptCommand,
9-
readVitePlusDirs,
109
removeVitePlusDirsFile,
10+
resolveVitePlusBinDir,
1111
supportsVitePlusDirs,
1212
VP_DIRS_FILE_ENV,
1313
} from "./ci/vp-dirs.js";
@@ -93,7 +93,7 @@ export async function installVitePlus(inputs: Inputs): Promise<void> {
9393
try {
9494
if (pinned.length > 0) {
9595
if (await tryUrls(pinned)) {
96-
ensureVitePlusBinInPath(dirsFile ? readVitePlusDirs(dirsFile)?.bin : undefined);
96+
ensureVitePlusBinInPath(dirsFile);
9797
return;
9898
}
9999
warning(
@@ -102,7 +102,7 @@ export async function installVitePlus(inputs: Inputs): Promise<void> {
102102
}
103103

104104
if (await tryUrls(latest)) {
105-
ensureVitePlusBinInPath(dirsFile ? readVitePlusDirs(dirsFile)?.bin : undefined);
105+
ensureVitePlusBinInPath(dirsFile);
106106
return;
107107
}
108108

@@ -124,10 +124,8 @@ async function runInstallCommand(url: string, env: { [key: string]: string }): P
124124
return exec(command, args, options);
125125
}
126126

127-
function ensureVitePlusBinInPath(resolvedBinDir?: string): void {
128-
// Vite+ releases before VpDirs cannot emit a directory dump and always use
129-
// the legacy monolithic layout.
130-
const binDir = resolvedBinDir ?? join(getVitePlusHome(), "bin");
127+
function ensureVitePlusBinInPath(dirsFile: string | undefined): void {
128+
const binDir = resolveVitePlusBinDir(dirsFile, join(getVitePlusHome(), "bin"));
131129
if (!process.env.PATH?.split(delimiter).includes(binDir)) {
132130
addPath(binDir);
133131
}

0 commit comments

Comments
 (0)