Skip to content

Commit 601f79a

Browse files
committed
fix(env): tailor activation instructions to available shells
1 parent 94b0645 commit 601f79a

9 files changed

Lines changed: 336 additions & 46 deletions

File tree

‎.github/scripts/test-install-bootstrap.ps1‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ $originalTemp = $env:TEMP
3737
$originalCheck = $env:VP_SELF_SETUP_SUPPORT_CHECK
3838
$originalPath = $env:Path
3939
$originalRegistry = $env:NPM_CONFIG_REGISTRY
40+
$originalVpShell = $env:VP_SHELL
4041
$fixtureSha = '0123456789012345678901234567890123456789'
4142
New-Item -ItemType Directory -Path "$testRoot/package", "$testRoot/tmp", "$testRoot/scripts" | Out-Null
4243
Set-Content -LiteralPath "$testRoot/package/vp.exe" -Value 'Payload fixture'
@@ -46,6 +47,8 @@ if ($args.Count -eq 0) {
4647
New-Item -ItemType File -Path "$testRoot/binary-invoked" | Out-Null
4748
if ($scenario -eq 'failure') { exit 42 }
4849
if ($env:VP_SELF_SETUP_SHELL -ne 'powershell') { exit 98 }
50+
$expectedVpShell = if ($scenario -eq 'supported-pr') { 'fish' } else { 'powershell' }
51+
if ($env:VP_SHELL -ne $expectedVpShell) { exit 96 }
4952
if ($scenario -eq 'supported-pr' -and $env:NPM_CONFIG_REGISTRY -ne 'https://registry-bridge.viteplus.dev/') { exit 97 }
5053
Write-Output ("`$script:InstallDir = '{0}'" -f "$testRoot/data")
5154
Write-Output ("`$script:ShimDir = '{0}'" -f "$testRoot/installed bin")
@@ -123,6 +126,8 @@ try {
123126
foreach ($scenario in @('supported', 'legacy', 'legacy-remote', 'legacy-failure', 'failure', 'pr', 'supported-pr')) {
124127
$env:Path = $originalPath
125128
$env:NPM_CONFIG_REGISTRY = 'https://custom.example'
129+
$initialVpShell = if ($scenario -eq 'supported-pr') { 'fish' } else { $null }
130+
$env:VP_SHELL = $initialVpShell
126131
$script:Requests = New-Object 'System.Collections.Generic.List[string]'
127132
$script:ExitCode = 0
128133
$script:PackageMetadata = $null
@@ -148,6 +153,7 @@ try {
148153
}
149154
$expectedExit = if ($scenario -in @('failure', 'legacy-failure')) { 42 } else { 0 }
150155
Assert ($script:ExitCode -eq $expectedExit) 'Binary exit code was lost'
156+
Assert ($env:VP_SHELL -eq $initialVpShell) 'Setup changed the caller shell'
151157
if ($scenario -eq 'supported') {
152158
Assert (($env:Path -split ';')[0] -eq "$testRoot/installed bin") 'Installed bin directory was not added to the current PATH'
153159
} elseif ($scenario -eq 'failure') {
@@ -169,6 +175,7 @@ try {
169175
$env:VP_SELF_SETUP_SUPPORT_CHECK = $originalCheck
170176
$env:Path = $originalPath
171177
$env:NPM_CONFIG_REGISTRY = $originalRegistry
178+
$env:VP_SHELL = $originalVpShell
172179
$env:TEMP = $originalTemp
173180
Remove-Item -LiteralPath $testRoot -Recurse -Force
174181
$global:LASTEXITCODE = 0

‎crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_env_activation/profile-guidance.mjs‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,24 @@ function setup(shell) {
5555
}
5656

5757
if (mode === 'powershell') {
58+
// Control executable discovery independently of the shells installed on the runner.
59+
const shellBin = path.resolve('profiles/shell-bin');
60+
fs.mkdirSync(shellBin, { recursive: true });
61+
fs.writeFileSync(
62+
path.join(shellBin, process.platform === 'win32' ? 'powershell.exe' : 'pwsh'),
63+
'',
64+
{
65+
mode: 0o755,
66+
},
67+
);
68+
env.PATH = shellBin;
69+
delete env.SHELL;
5870
for (const shell of ['pwsh', undefined]) {
5971
const output = setup(shell);
6072
assert.match(output, /\. '[^\n]*env\.ps1'/);
6173
assert.match(output, /\$PROFILE if it is not already there/);
6274
assert.doesNotMatch(output, /Or open a new terminal/);
75+
assert.doesNotMatch(output, /Fish:|Nushell:/);
6376
console.log(output.split('\n').find((line) => line.includes('$PROFILE')));
6477
}
6578
} else if (mode.startsWith('cmd')) {
@@ -163,7 +176,8 @@ if (mode === 'powershell') {
163176
if (mode === 'unset' || mode === 'unrecognized') {
164177
console.log('Only Fish is configured:');
165178
const output = setup(mode === 'unset' ? undefined : mode);
166-
assert.match(output, /If your shell profile does not already load Vite\+/);
179+
assert.match(output, /For Bash, run:/);
180+
assert.match(output, /If your ~\/\.bashrc does not already load Vite\+/);
167181
assert.doesNotMatch(output, /Or open a new terminal/);
168182
console.log(output);
169183
freshBash(path.join(system, 'node'));
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import assert from 'node:assert/strict';
2+
import { spawnSync } from 'node:child_process';
3+
import fs from 'node:fs';
4+
import path from 'node:path';
5+
6+
const mode = process.argv[2];
7+
const home = path.resolve('shell-guidance/user');
8+
const shellBin = path.resolve('shell-guidance/bin');
9+
const binary = path.join(process.env.VP_HOME, 'current/bin/vp');
10+
const env = { ...process.env, HOME: home, ZDOTDIR: home, PATH: shellBin };
11+
delete env.VP_SHELL;
12+
delete env.SHELL;
13+
fs.mkdirSync(home, { recursive: true });
14+
fs.mkdirSync(shellBin, { recursive: true });
15+
16+
/** @returns {string} */
17+
function capture(args, extra = {}) {
18+
const result = spawnSync(binary, args, {
19+
env: { ...env, ...extra },
20+
encoding: 'utf8',
21+
timeout: 30000,
22+
});
23+
assert.equal(result.status, 0, result.error?.message ?? result.stdout + result.stderr);
24+
return result.stdout.replace(/\u001b\[[0-9;]*m/g, '').replaceAll('\r\n', '\n');
25+
}
26+
27+
const dirs = Object.fromEntries(
28+
capture([], { VP_DUMP_DIRS: '1' })
29+
.trim()
30+
.split('\n')
31+
.map((line) => line.split('\t')),
32+
);
33+
34+
/** @returns {string} */
35+
function setup() {
36+
const output = capture(['env', 'setup']);
37+
const heading = output.indexOf('Next Steps:\n');
38+
assert.ok(heading >= 0, output);
39+
const instructions = output.slice(heading).trimEnd();
40+
assert.doesNotMatch(instructions, /Or open a new terminal/);
41+
console.log(instructions);
42+
return instructions;
43+
}
44+
45+
if (mode === 'hints') {
46+
// Even matching profiles must not turn a login-shell hint into a current-shell guarantee.
47+
for (const profile of ['.zshrc', '.bashrc']) {
48+
fs.writeFileSync(path.join(home, profile), `. "${dirs.config}/env"\n`);
49+
}
50+
for (const [shell, label, file] of [
51+
['zsh', 'Zsh', 'env'],
52+
['bash', 'Bash', 'env'],
53+
['sh', 'sh', 'env'],
54+
['fish', 'Fish', 'env.fish'],
55+
['nu', 'Nushell', 'env.nu'],
56+
['pwsh', 'PowerShell', 'env.ps1'],
57+
]) {
58+
env.SHELL = `/usr/bin/${shell}`;
59+
console.log(`SHELL=${env.SHELL}, VP_SHELL=<unset>:`);
60+
const output = setup();
61+
assert.ok(output.includes(`For ${label}, run:`));
62+
assert.ok(output.includes(path.join(dirs.config, file)));
63+
assert.equal(output.split('\n').filter((line) => line.includes(dirs.config)).length, 1);
64+
assert.doesNotMatch(output, /Activate Vite\+ in this terminal/);
65+
}
66+
env.SHELL = '/bin/zsh';
67+
env.VP_SHELL = 'unrecognized';
68+
console.log('SHELL=/bin/zsh, VP_SHELL=unrecognized:');
69+
assert.match(setup(), /For Zsh, run:/);
70+
env.VP_SHELL = 'fish';
71+
console.log('SHELL=/bin/zsh, VP_SHELL=fish (Fish is not on PATH):');
72+
const output = setup();
73+
assert.match(output, /Activate Vite\+ in this terminal:/);
74+
assert.match(output, /env\.fish/);
75+
assert.doesNotMatch(output, /For Zsh|env\.ps1|env\.nu/);
76+
} else {
77+
assert.equal(mode, 'available');
78+
env.SHELL = '/bin/unrecognized';
79+
for (const shell of ['fish', 'nu', 'pwsh']) {
80+
fs.writeFileSync(path.join(shellBin, shell), '', { mode: 0o644 });
81+
}
82+
console.log('Unknown shell; optional shell files are not executable:');
83+
assert.doesNotMatch(setup(), /Fish:|Nushell:|PowerShell:|\$PROFILE/);
84+
for (const [shell, label] of [
85+
['fish', 'Fish'],
86+
['nu', 'Nushell'],
87+
['pwsh', 'PowerShell'],
88+
]) {
89+
fs.chmodSync(path.join(shellBin, shell), 0o755);
90+
console.log(`Unknown shell; only ${label} is available:`);
91+
const output = setup();
92+
assert.ok(output.includes(`${label}:`));
93+
for (const other of ['Fish', 'Nushell', 'PowerShell'].filter((name) => name !== label)) {
94+
assert.ok(!output.includes(`${other}:`));
95+
}
96+
assert.equal(output.includes('$PROFILE'), shell === 'pwsh');
97+
fs.chmodSync(path.join(shellBin, shell), 0o644);
98+
}
99+
}

‎crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_env_activation/snapshots.toml‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,18 @@ steps = [
5353
["node", "profile-guidance.mjs", "bash-interactive"],
5454
]
5555

56+
[[case]]
57+
name = "activation_login_shell_hint"
58+
vp = "global"
59+
skip-platforms = ["windows"]
60+
steps = [["node", "shell-guidance.mjs", "hints"]]
61+
62+
[[case]]
63+
name = "activation_available_shells"
64+
vp = "global"
65+
skip-platforms = ["windows"]
66+
steps = [["node", "shell-guidance.mjs", "available"]]
67+
5668
[[case]]
5769
name = "activation_powershell_profile_guidance"
5870
vp = "global"
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# activation_available_shells
2+
3+
## `node shell-guidance.mjs available`
4+
5+
```
6+
Unknown shell; optional shell files are not executable:
7+
Next Steps:
8+
Activate Vite+ in this terminal:
9+
Bash/Zsh: . "<home>/.vite-plus/env"
10+
11+
If your shell profile does not already load Vite+, add the command for your shell.
12+
13+
Restart an already-running IDE to load its environment. Run `vp env doctor` to verify.
14+
Unknown shell; only Fish is available:
15+
Next Steps:
16+
Activate Vite+ in this terminal:
17+
Bash/Zsh: . "<home>/.vite-plus/env"
18+
Fish: source "<home>/.vite-plus/env.fish"
19+
20+
If your shell profile does not already load Vite+, add the command for your shell.
21+
22+
Restart an already-running IDE to load its environment. Run `vp env doctor` to verify.
23+
Unknown shell; only Nushell is available:
24+
Next Steps:
25+
Activate Vite+ in this terminal:
26+
Bash/Zsh: . "<home>/.vite-plus/env"
27+
Nushell: source "<home>/.vite-plus/env.nu"
28+
29+
If your shell profile does not already load Vite+, add the command for your shell.
30+
31+
Restart an already-running IDE to load its environment. Run `vp env doctor` to verify.
32+
Unknown shell; only PowerShell is available:
33+
Next Steps:
34+
Activate Vite+ in this terminal:
35+
Bash/Zsh: . "<home>/.vite-plus/env"
36+
PowerShell: . '<home>/.vite-plus/env.ps1'
37+
38+
If your shell profile does not already load Vite+, add the command for your shell.
39+
For PowerShell, add its command to $PROFILE if it is not already there.
40+
41+
Restart an already-running IDE to load its environment. Run `vp env doctor` to verify.
42+
```
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# activation_login_shell_hint
2+
3+
## `node shell-guidance.mjs hints`
4+
5+
```
6+
SHELL=/usr/bin/zsh, VP_SHELL=<unset>:
7+
Next Steps:
8+
For Zsh, run:
9+
. "<home>/.vite-plus/env"
10+
11+
If your .zshrc does not already load Vite+, add this command.
12+
13+
Restart an already-running IDE to load its environment. Run `vp env doctor` to verify.
14+
SHELL=/usr/bin/bash, VP_SHELL=<unset>:
15+
Next Steps:
16+
For Bash, run:
17+
. "<home>/.vite-plus/env"
18+
19+
If your ~/.bashrc does not already load Vite+, add this command for interactive non-login Bash sessions.
20+
Login Bash shells must also load the command through their login profile.
21+
22+
Restart an already-running IDE to load its environment. Run `vp env doctor` to verify.
23+
SHELL=/usr/bin/sh, VP_SHELL=<unset>:
24+
Next Steps:
25+
For sh, run:
26+
. "<home>/.vite-plus/env"
27+
28+
If your shell profile does not already load Vite+, add the command for your shell.
29+
30+
Restart an already-running IDE to load its environment. Run `vp env doctor` to verify.
31+
SHELL=/usr/bin/fish, VP_SHELL=<unset>:
32+
Next Steps:
33+
For Fish, run:
34+
source "<home>/.vite-plus/env.fish"
35+
36+
If your shell profile does not already load Vite+, add the command for your shell.
37+
38+
Restart an already-running IDE to load its environment. Run `vp env doctor` to verify.
39+
SHELL=/usr/bin/nu, VP_SHELL=<unset>:
40+
Next Steps:
41+
For Nushell, run:
42+
source "<home>/.vite-plus/env.nu"
43+
44+
If your shell profile does not already load Vite+, add the command for your shell.
45+
46+
Restart an already-running IDE to load its environment. Run `vp env doctor` to verify.
47+
SHELL=/usr/bin/pwsh, VP_SHELL=<unset>:
48+
Next Steps:
49+
For PowerShell, run:
50+
. '<home>/.vite-plus/env.ps1'
51+
52+
Add this command to $PROFILE if it is not already there, for future PowerShell sessions.
53+
54+
Restart an already-running IDE to load its environment. Run `vp env doctor` to verify.
55+
SHELL=/bin/zsh, VP_SHELL=unrecognized:
56+
Next Steps:
57+
For Zsh, run:
58+
. "<home>/.vite-plus/env"
59+
60+
If your .zshrc does not already load Vite+, add this command.
61+
62+
Restart an already-running IDE to load its environment. Run `vp env doctor` to verify.
63+
SHELL=/bin/zsh, VP_SHELL=fish (Fish is not on PATH):
64+
Next Steps:
65+
Activate Vite+ in this terminal:
66+
source "<home>/.vite-plus/env.fish"
67+
68+
Add the command for your shell to its profile to activate future terminals.
69+
70+
Restart an already-running IDE to load its environment. Run `vp env doctor` to verify.
71+
```

‎crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_env_activation/snapshots/activation_profile_guidance.md‎

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,11 @@
66
Only Fish is configured:
77
VP_SHELL=<unset>:
88
Next Steps:
9-
Activate Vite+ in this terminal:
10-
Bash/Zsh: . "<home>/.vite-plus/env"
11-
Fish: source "<home>/.vite-plus/env.fish"
12-
Nushell: source "<home>/.vite-plus/env.nu"
13-
PowerShell: . '<home>/.vite-plus/env.ps1'
9+
For Bash, run:
10+
. "<home>/.vite-plus/env"
1411
15-
If your shell profile does not already load Vite+, add the command for your shell.
16-
For PowerShell, add its command to $PROFILE if it is not already there.
12+
If your ~/.bashrc does not already load Vite+, add this command for interactive non-login Bash sessions.
13+
Login Bash shells must also load the command through their login profile.
1714
1815
Restart an already-running IDE to load its environment. Run `vp env doctor` to verify.
1916
$ bash --noprofile -ic 'command -v node; node --version'
@@ -27,14 +24,11 @@ system-node
2724
Only Fish is configured:
2825
VP_SHELL=unrecognized:
2926
Next Steps:
30-
Activate Vite+ in this terminal:
31-
Bash/Zsh: . "<home>/.vite-plus/env"
32-
Fish: source "<home>/.vite-plus/env.fish"
33-
Nushell: source "<home>/.vite-plus/env.nu"
34-
PowerShell: . '<home>/.vite-plus/env.ps1'
27+
For Bash, run:
28+
. "<home>/.vite-plus/env"
3529
36-
If your shell profile does not already load Vite+, add the command for your shell.
37-
For PowerShell, add its command to $PROFILE if it is not already there.
30+
If your ~/.bashrc does not already load Vite+, add this command for interactive non-login Bash sessions.
31+
Login Bash shells must also load the command through their login profile.
3832
3933
Restart an already-running IDE to load its environment. Run `vp env doctor` to verify.
4034
$ bash --noprofile -ic 'command -v node; node --version'

0 commit comments

Comments
 (0)