Skip to content

Commit a8f9aba

Browse files
authored
feat: resolve Vite+ executable paths from VpDirs (#131)
The action reads Vite+'s executable directory from the installed payload. Vite+ 0.3.0 prerelease builds and later versions use `VP_DUMP_DIRS=1`. Preview builds also use this check. Versions before the 0.3 release line use the original installer command and the legacy executable directory. GitHub Actions, Azure Pipelines, and GitLab use the same version check. Closes #129.
1 parent 92568af commit a8f9aba

11 files changed

Lines changed: 981 additions & 164 deletions

File tree

.github/workflows/test.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,11 @@ jobs:
8282

8383
- name: Verify installation
8484
shell: bash
85-
run: vp --version | grep -F "${{ steps.preview.outputs.version }}"
85+
run: |
86+
vp env current
87+
vp i -g cowsay@1.6.0
88+
vp env doctor
89+
vp --version | grep -F "${{ steps.preview.outputs.version }}"
8690
8791
test-node-version:
8892
strategy:

dist/azure/index.mjs

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

dist/index.mjs

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

gitlab/bootstrap.sh

Lines changed: 130 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,20 +37,99 @@ 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+
65+
setup_vp_read_installed_version() {
66+
awk '
67+
$1 == "vp" && $2 ~ /^v?[0-9]/ {
68+
version = $2
69+
sub(/^v/, "", version)
70+
print version
71+
exit
72+
}
73+
{
74+
for (field = 1; field < NF; field++) {
75+
if ($field == "Global:" && $(field + 1) ~ /^v?[0-9]/) {
76+
version = $(field + 1)
77+
sub(/^v/, "", version)
78+
print version
79+
exit
80+
}
81+
}
82+
}
83+
' "$1"
84+
}
85+
86+
setup_vp_install_and_dump_dirs() {
87+
bash -c '
88+
set +u
89+
setup_vp_installer_path="$1"
90+
setup_vp_dirs_path="$2"
91+
set --
92+
93+
# Run the sourced installer at the top level of this shell. The installer
94+
# can then use its own errexit setting even when the caller checks the
95+
# result in an if condition. Nounset is disabled only in this shell.
96+
. "$setup_vp_installer_path"
97+
setup_vp_installer_status=$?
98+
if [ "$setup_vp_installer_status" -ne 0 ]; then
99+
exit "$setup_vp_installer_status"
100+
fi
101+
102+
setup_vp_shim_dir="${SHIM_DIR:-${INSTALL_DIR:-${VP_HOME:-$HOME/.vite-plus}}/bin}"
103+
if [ ! -x "$setup_vp_shim_dir/vp" ]; then
104+
exit 1
105+
fi
106+
107+
"$setup_vp_shim_dir/vp" --version > "$setup_vp_dirs_path"
108+
VP_DUMP_DIRS=1 "$setup_vp_shim_dir/vp" >> "$setup_vp_dirs_path"
109+
' setup-vp-installer "$1" "$2"
110+
}
111+
40112
setup_vp_install_viteplus_from() {
41113
setup_vp_url="$1"
42114
setup_vp_download "$setup_vp_url" "$setup_vp_install_tmp" || return 1
43115

44-
if [ -n "$setup_vp_pr_version" ]; then
45-
VP_VERSION="$SETUP_VP_VERSION" \
46-
VITE_PLUS_VERSION="$SETUP_VP_VERSION" \
47-
VP_PR_VERSION="$setup_vp_pr_version" \
48-
bash "$setup_vp_install_tmp"
49-
else
50-
VP_VERSION="$SETUP_VP_VERSION" \
51-
VITE_PLUS_VERSION="$SETUP_VP_VERSION" \
116+
(
117+
export VP_VERSION="$SETUP_VP_VERSION"
118+
export VITE_PLUS_VERSION="$SETUP_VP_VERSION"
119+
if [ -n "$setup_vp_pr_version" ]; then
120+
export VP_PR_VERSION="$setup_vp_pr_version"
121+
fi
122+
123+
if [ "$setup_vp_detect_dirs" = "true" ]; then
124+
: > "$setup_vp_dirs_tmp"
125+
export VP_VPDIRS_AWARE="1"
126+
# Source the official installer so its VpDirs-resolved shim remains
127+
# available long enough to ask the installed payload for its directories.
128+
setup_vp_install_and_dump_dirs "$setup_vp_install_tmp" "$setup_vp_dirs_tmp"
129+
else
52130
bash "$setup_vp_install_tmp"
53-
fi
131+
fi
132+
)
54133
}
55134

56135
setup_vp_try_install_urls() {
@@ -122,6 +201,23 @@ if [[ "$SETUP_VP_VERSION" =~ ^0\.0\.0-commit\.([0-9a-fA-F]{40})$ ]]; then
122201
setup_vp_pr_version="${BASH_REMATCH[1]}"
123202
fi
124203

204+
# VpDirs was added in Vite+ 0.3.0. Preview builds also contain it. For a
205+
# dist-tag, probe the installed version before a missing VpDirs result selects
206+
# the legacy layout.
207+
setup_vp_detect_dirs="true"
208+
setup_vp_check_installed_version="false"
209+
if [ -z "$setup_vp_pr_version" ]; then
210+
if [[ "$SETUP_VP_VERSION" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)(-[0-9A-Za-z.-]+)?$ ]]; then
211+
setup_vp_major=$((10#${BASH_REMATCH[1]}))
212+
setup_vp_minor=$((10#${BASH_REMATCH[2]}))
213+
if [ "$setup_vp_major" -eq 0 ] && [ "$setup_vp_minor" -lt 3 ]; then
214+
setup_vp_detect_dirs="false"
215+
fi
216+
else
217+
setup_vp_check_installed_version="true"
218+
fi
219+
fi
220+
125221
# Git ref that serves the install script for the requested version: the
126222
# preview build's commit, or the `v<version>` release tag for an exact
127223
# version. Dist-tags like "latest" do not map to a ref and keep the latest
@@ -133,12 +229,35 @@ elif [[ "$SETUP_VP_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; th
133229
setup_vp_pinned_ref="v${SETUP_VP_VERSION}"
134230
fi
135231
setup_vp_install_tmp="$(mktemp "${TMPDIR:-/tmp}/setup-vp-install.XXXXXX")"
232+
setup_vp_dirs_tmp="$(mktemp "${TMPDIR:-/tmp}/setup-vp-dirs.XXXXXX")"
136233
setup_vp_runtime_dir="$(mktemp -d "${TMPDIR:-/tmp}/setup-vp-gitlab-runtime.XXXXXX")"
137234
setup_vp_runtime_tmp="${setup_vp_runtime_dir}/index.mjs"
138-
trap 'rm -f "$setup_vp_install_tmp" "$setup_vp_runtime_tmp"; rmdir "$setup_vp_runtime_dir" 2>/dev/null || true' EXIT
235+
trap 'rm -f "$setup_vp_install_tmp" "$setup_vp_dirs_tmp" "$setup_vp_runtime_tmp"; rmdir "$setup_vp_runtime_dir" 2>/dev/null || true' EXIT
139236

140237
setup_vp_install_viteplus
141-
export PATH="$HOME/.vite-plus/bin:$PATH"
238+
if [ "$setup_vp_detect_dirs" = "true" ]; then
239+
if ! setup_vp_bin_dir="$(setup_vp_read_bin_dir "$setup_vp_dirs_tmp")"; then
240+
setup_vp_bin_dir=""
241+
if [ "$setup_vp_check_installed_version" = "true" ]; then
242+
setup_vp_installed_version="$(setup_vp_read_installed_version "$setup_vp_dirs_tmp")"
243+
if [[ "$setup_vp_installed_version" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)(-[0-9A-Za-z.-]+)?$ ]]; then
244+
setup_vp_major=$((10#${BASH_REMATCH[1]}))
245+
setup_vp_minor=$((10#${BASH_REMATCH[2]}))
246+
if [ "$setup_vp_major" -eq 0 ] && [ "$setup_vp_minor" -lt 3 ]; then
247+
setup_vp_bin_dir="$HOME/.vite-plus/bin"
248+
fi
249+
fi
250+
fi
251+
if [ -z "$setup_vp_bin_dir" ]; then
252+
echo "setup-vp: Vite+ was installed successfully, but setup-vp could not resolve its VpDirs." >&2
253+
return 1 2>/dev/null || exit 1
254+
fi
255+
fi
256+
else
257+
# Vite+ releases before VpDirs use the monolithic layout.
258+
setup_vp_bin_dir="$HOME/.vite-plus/bin"
259+
fi
260+
export PATH="$setup_vp_bin_dir:$PATH"
142261
setup_vp_export_env PATH "$PATH"
143262

144263
if ! command -v node >/dev/null 2>&1; then

src/azure/install-viteplus.test.ts

Lines changed: 101 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,27 @@
11
import { describe, expect, it, vi } from "vite-plus/test";
2+
import { writeFileSync } from "node:fs";
23
import { installVitePlus } from "./install-viteplus.js";
34

5+
function writeDirsFile(env: Record<string, string>, bin: string): void {
6+
writeFileSync(
7+
env.SETUP_VP_DIRS_FILE,
8+
[
9+
"data\t/test/data",
10+
`bin\t${bin}`,
11+
"cache\t/test/cache",
12+
"config\t/test/config",
13+
"state\t/test/state",
14+
].join("\n"),
15+
);
16+
}
17+
418
describe("installVitePlus", () => {
519
it("uses PowerShell installers on Windows and bash installers on Unix", async () => {
620
const calls: NodeJS.Platform[] = [];
721
const runInstall = vi.fn(
8-
(
9-
_url: string,
10-
_env: Record<string, string>,
11-
platform: NodeJS.Platform = process.platform,
12-
) => {
22+
(_url: string, env: Record<string, string>, platform: NodeJS.Platform = process.platform) => {
1323
calls.push(platform);
24+
writeDirsFile(env, `/test/${platform}/bin`);
1425
return 0;
1526
},
1627
);
@@ -39,13 +50,17 @@ describe("installVitePlus", () => {
3950

4051
it("installs exact versions with the release-tag script before the latest script", async () => {
4152
// Fail every attempt so the full URL order is observable.
42-
const runInstall = vi.fn((_url: string) => 1);
53+
const runInstall = vi.fn((_url: string, _env: Record<string, string>) => 1);
4354
const warnings: string[] = [];
4455

4556
await expect(
4657
installVitePlus("0.2.9", {
4758
platform: "linux",
48-
env: { PATH: "" },
59+
env: {
60+
PATH: "",
61+
VP_VPDIRS_AWARE: "1",
62+
SETUP_VP_DIRS_FILE: "/tmp/stale-vp-dirs",
63+
},
4964
prependPath: () => undefined,
5065
sleep: async () => undefined,
5166
runInstall,
@@ -58,10 +73,16 @@ describe("installVitePlus", () => {
5873
expect(urls[1]).toContain("jsdelivr");
5974
expect(urls[4]).toBe("https://viteplus.dev/install.sh");
6075
expect(warnings.some((message) => message.includes("Falling back to the latest"))).toBe(true);
76+
const installEnv = runInstall.mock.calls[0]?.[1] as Record<string, string>;
77+
expect(installEnv.VP_VPDIRS_AWARE).toBeUndefined();
78+
expect(installEnv.SETUP_VP_DIRS_FILE).toBeUndefined();
6179
});
6280

6381
it("routes pkg.pr.new commit builds through VP_PR_VERSION", async () => {
64-
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+
});
6586
const sha = "a".repeat(40);
6687

6788
await installVitePlus(`0.0.0-commit.${sha}`, {
@@ -77,5 +98,77 @@ describe("installVitePlus", () => {
7798
[string, Record<string, string>, NodeJS.Platform?]
7899
>;
79100
expect(installCalls[0]?.[1]?.VP_PR_VERSION).toBe(sha);
101+
expect(installCalls[0]?.[1]?.VP_VPDIRS_AWARE).toBe("1");
102+
expect(installCalls[0]?.[1]?.SETUP_VP_DIRS_FILE).toMatch(/setup-vp-dirs-.*\.txt$/);
103+
});
104+
105+
it("uses the installed version to resolve the latest dist-tag", async () => {
106+
const prependPath = vi.fn();
107+
const env = { HOME: "/home/runner", PATH: "/usr/bin" };
108+
const runInstall = vi.fn((_url: string, installEnv: Record<string, string>) => {
109+
writeFileSync(installEnv.SETUP_VP_DIRS_FILE, "vp v0.2.9\n");
110+
return 0;
111+
});
112+
113+
await installVitePlus("latest", {
114+
platform: "linux",
115+
env,
116+
prependPath,
117+
sleep: async () => undefined,
118+
runInstall,
119+
logWarningFn: () => undefined,
120+
});
121+
122+
expect(prependPath).toHaveBeenCalledWith("/home/runner/.vite-plus/bin");
123+
expect(env.PATH).toBe("/home/runner/.vite-plus/bin:/usr/bin");
124+
});
125+
126+
it.each([
127+
{ version: "0.3.0", output: "vp v0.2.9\n" },
128+
{ version: `0.0.0-commit.${"a".repeat(40)}`, output: "bin\t/test/data/bin\n" },
129+
{ version: "latest", output: "vp v0.3.0\n" },
130+
])("fails when $version does not report valid VpDirs", async ({ version, output }) => {
131+
const prependPath = vi.fn();
132+
const runInstall = vi.fn((_url: string, env: Record<string, string>) => {
133+
if (output !== undefined) writeFileSync(env.SETUP_VP_DIRS_FILE, output);
134+
return 0;
135+
});
136+
137+
await expect(
138+
installVitePlus(version, {
139+
platform: "linux",
140+
env: { PATH: "/usr/bin" },
141+
prependPath,
142+
sleep: async () => undefined,
143+
runInstall,
144+
logWarningFn: () => undefined,
145+
}),
146+
).rejects.toThrow(
147+
"Vite+ was installed successfully, but setup-vp could not resolve its VpDirs.",
148+
);
149+
150+
expect(runInstall).toHaveBeenCalledTimes(1);
151+
expect(prependPath).not.toHaveBeenCalled();
152+
});
153+
154+
it("prepends the bin directory reported by the installed payload", async () => {
155+
const prependPath = vi.fn();
156+
const env = { PATH: "/usr/bin" };
157+
const runInstall = vi.fn((_url: string, installEnv: Record<string, string>) => {
158+
writeDirsFile(installEnv, "/test/data/bin");
159+
return 0;
160+
});
161+
162+
await installVitePlus("latest", {
163+
platform: "linux",
164+
env,
165+
prependPath,
166+
sleep: async () => undefined,
167+
runInstall,
168+
logWarningFn: () => undefined,
169+
});
170+
171+
expect(prependPath).toHaveBeenCalledWith("/test/data/bin");
172+
expect(env.PATH).toBe("/test/data/bin:/usr/bin");
80173
});
81174
});

0 commit comments

Comments
 (0)