Skip to content

Commit d2d087c

Browse files
committed
refactor(env): simplify setup guidance and fixtures
1 parent 3657f25 commit d2d087c

2 files changed

Lines changed: 81 additions & 72 deletions

File tree

  • crates
    • vp_cli_snapshots/tests/cli_snapshots/fixtures/command_env_activation
    • vp_global_cli/src/commands/env

‎crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_env_activation/verify.mjs‎

Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ if (kind === 'xdg') {
5555
} else {
5656
env.VP_HOME = path.join(root, kind === 'external' ? 'home' : 'home ' + special);
5757
}
58-
function captured(args, extra = {}) {
58+
/** @returns {import('node:child_process').SpawnSyncReturns<string>} */
59+
function captureVp(args, extra = {}) {
5960
const result = spawnSync(binary, args, {
6061
env: { ...env, ...extra },
6162
encoding: 'utf8',
@@ -64,20 +65,19 @@ function captured(args, extra = {}) {
6465
assert.equal(result.status, 0, result.error?.message ?? result.stdout + result.stderr);
6566
return result;
6667
}
67-
function terminal(executable, args) {
68+
/** @returns {void} */
69+
function runInTerminal(executable, args) {
6870
const result = spawnSync(executable, args, { env, stdio: 'inherit', timeout: 30000 });
6971
assert.equal(result.status, 0, result.error?.message);
7072
}
7173
const dirs = Object.fromEntries(
72-
captured([], { VP_DUMP_DIRS: '1' })
74+
captureVp([], { VP_DUMP_DIRS: '1' })
7375
.stdout.trim()
7476
.split('\n')
7577
.map((line) => line.split('\t')),
7678
);
7779
// Query the authoritative resolver instead of assuming a platform's directory layout.
78-
const bin = dirs.bin;
79-
const config = dirs.config;
80-
const data = dirs.data;
80+
const { bin, config, data } = dirs;
8181
assert.ok(bin && config && data, JSON.stringify(dirs));
8282
const runtime = path.join(data, 'js_runtime/node', version, 'bin');
8383
fs.mkdirSync(runtime, { recursive: true });
@@ -102,20 +102,24 @@ console.log('First setup:');
102102
let activation;
103103
if (kind === 'external') {
104104
console.log('$ vp env list node');
105-
terminal(binary, ['env', 'list', 'node']);
105+
runInTerminal(binary, ['env', 'list', 'node']);
106106
// The full PTY snapshot records the printed command. Other cases also parse
107107
// the guidance and execute it, so escaping remains covered independently.
108108
const escaped = path.join(config, 'env').replace(/[\\$`"]/g, '\\$&');
109109
activation = `. "${escaped}"`;
110110
} else {
111-
const first = captured(['env', 'list', 'node']);
111+
const first = captureVp(['env', 'list', 'node']);
112112
const setup = first.stderr.replace(/\u001b\[[0-9;]*m/g, '');
113113
assert.match(setup, /Vite\+ setup complete/);
114-
if (automaticProfile) assert.doesNotMatch(setup, /Add the command/);
115-
else assert.match(setup, /Add the command/);
114+
if (automaticProfile) {
115+
assert.doesNotMatch(setup, /Add the command/);
116+
} else {
117+
assert.match(setup, /Add the command/);
118+
}
119+
const sourcePrefix = shell === 'fish' || shell === 'nu' ? 'source "' : '. "';
116120
activation = setup
117121
.split('\n')
118-
.find((line) => line.trim().startsWith(shell === 'fish' || shell === 'nu' ? 'source "' : '. "'))
122+
.find((line) => line.trim().startsWith(sourcePrefix))
119123
?.trim();
120124
assert.ok(activation, setup);
121125
console.log(
@@ -125,14 +129,20 @@ if (kind === 'external') {
125129
.join('\n'),
126130
);
127131
}
128-
const profile =
129-
shell === 'zsh'
130-
? path.join(env.ZDOTDIR, '.zshrc')
131-
: shell === 'fish'
132-
? path.join(env.XDG_CONFIG_HOME, 'fish/config.fish')
133-
: shell === 'nu'
134-
? path.join(env.XDG_CONFIG_HOME, 'nushell/config.nu')
135-
: path.join(env.HOME, '.bashrc');
132+
let profile;
133+
switch (shell) {
134+
case 'zsh':
135+
profile = path.join(env.ZDOTDIR, '.zshrc');
136+
break;
137+
case 'fish':
138+
profile = path.join(env.XDG_CONFIG_HOME, 'fish/config.fish');
139+
break;
140+
case 'nu':
141+
profile = path.join(env.XDG_CONFIG_HOME, 'nushell/config.nu');
142+
break;
143+
default:
144+
profile = path.join(env.HOME, '.bashrc');
145+
}
136146
if (automaticProfile) {
137147
assert.ok(fs.readFileSync(profile, 'utf8').includes(activation));
138148
} else {
@@ -142,9 +152,9 @@ if (automaticProfile) {
142152
console.log('Setup with an existing profile entry:');
143153
if (kind === 'external') {
144154
console.log('$ vp env setup');
145-
terminal(binary, ['env', 'setup']);
155+
runInTerminal(binary, ['env', 'setup']);
146156
} else {
147-
const repeated = captured(['env', 'setup']).stdout.replace(/\u001b\[[0-9;]*m/g, '');
157+
const repeated = captureVp(['env', 'setup']).stdout.replace(/\u001b\[[0-9;]*m/g, '');
148158
assert.doesNotMatch(repeated, /Add the command/);
149159
assert.match(repeated, /open a new terminal/);
150160
console.log(repeated.split('\n').find((line) => line.includes('open a new terminal')));
@@ -155,17 +165,15 @@ Object.assign(env, {
155165
});
156166
console.log('Same terminal:');
157167
if (shell === 'fish') {
158-
terminal(shellBin, ['--no-config', 'session.fish']);
168+
runInTerminal(shellBin, ['--no-config', 'session.fish']);
159169
} else if (shell === 'nu') {
160170
fs.writeFileSync(
161171
'activate.nu',
162172
fs.readFileSync('session.nu', 'utf8').replaceAll('__ACTIVATION_COMMAND__', activation),
163173
);
164-
terminal(shellBin, ['--no-config-file', 'activate.nu']);
174+
runInTerminal(shellBin, ['--no-config-file', 'activate.nu']);
175+
} else if (shell === 'bash') {
176+
runInTerminal(shellBin, ['--noprofile', '--norc', 'session.sh']);
165177
} else {
166-
terminal(shellBin, [
167-
shell === 'bash' ? '--noprofile' : '-f',
168-
...(shell === 'bash' ? ['--norc'] : []),
169-
'session.sh',
170-
]);
178+
runInTerminal(shellBin, ['-f', 'session.sh']);
171179
}

‎crates/vp_global_cli/src/commands/env/setup.rs‎

Lines changed: 45 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,16 @@ impl EnvShell {
5555
EnvShell::Powershell => "env.ps1",
5656
}
5757
}
58+
59+
fn source_command(self, env_dir: &vt_path::AbsolutePath) -> String {
60+
let path = env_dir.join(self.env_file_name()).to_string();
61+
match self {
62+
Self::Posix => format!(". \"{}\"", escape_posix_double_quoted_string(&path)),
63+
Self::Fish => format!("source \"{}\"", escape_fish_double_quoted_string(&path)),
64+
Self::Nu => format!("source \"{}\"", escape_nu_double_quoted_string(&path)),
65+
Self::Powershell => format!(". '{}'", escape_powershell_single_quoted_string(&path)),
66+
}
67+
}
5868
}
5969

6070
/// Execute the setup command.
@@ -1116,7 +1126,7 @@ async fn create_env_files() -> Result<(), Error> {
11161126
/// Inspect the profiles setup writes, without changing them or using a saved flag.
11171127
fn has_configured_profile(config: &vp_shared::EnvConfig) -> bool {
11181128
let shell = config.vp_shell.as_deref().map(str::to_ascii_lowercase);
1119-
ALL_SHELL_PROFILES.iter().any(|profile| {
1129+
for profile in ALL_SHELL_PROFILES {
11201130
let relevant = match shell.as_deref() {
11211131
Some("zsh") => matches!(profile.root, ShellProfileRoot::Zsh),
11221132
Some("bash") => matches!(profile.root, ShellProfileRoot::Home),
@@ -1126,51 +1136,54 @@ fn has_configured_profile(config: &vp_shared::EnvConfig) -> bool {
11261136
_ => true,
11271137
};
11281138
if !relevant {
1129-
return false;
1139+
continue;
11301140
}
11311141
let path = resolve_profile_path(profile, &config.user_home);
1132-
let Ok(content) = std::fs::read_to_string(path) else { return false };
1142+
let Ok(content) = std::fs::read_to_string(path) else { continue };
11331143
let env_file = config.dirs.config.join(profile.env_file);
11341144
let absolute = env_file.to_string();
1135-
let relative = render_home_relative_path(env_file.as_path(), config.user_home.as_path());
1145+
let home_relative =
1146+
render_home_relative_path(env_file.as_path(), config.user_home.as_path());
11361147
let escape = match profile.env_file {
11371148
"env.fish" => escape_fish_double_quoted_string,
11381149
"env.nu" => escape_nu_double_quoted_string,
11391150
_ => escape_posix_double_quoted_string,
11401151
};
1141-
let relative = if profile.env_file == "env.nu" {
1142-
escape(&render_nu_path_ref(&relative))
1152+
let escaped_relative = if profile.env_file == "env.nu" {
1153+
escape(&render_nu_path_ref(&home_relative))
11431154
} else {
1144-
escape_home_relative_double_quoted_path(&relative, escape)
1155+
escape_home_relative_double_quoted_path(&home_relative, escape)
11451156
};
11461157
let mut arguments = vec![
11471158
format!("\"{}\"", escape(&absolute)),
11481159
format!("'{absolute}'"),
11491160
absolute,
1150-
format!("\"{relative}\""),
1151-
format!("\"{}\"", relative.replacen("$HOME", "${HOME}", 1)),
1152-
render_nu_path_ref(&relative),
1161+
format!("\"{escaped_relative}\""),
1162+
format!("\"{}\"", escaped_relative.replacen("$HOME", "${HOME}", 1)),
1163+
render_nu_path_ref(&escaped_relative),
11531164
];
11541165
if profile.env_file == "env.nu" {
1155-
let relative =
1156-
render_home_relative_path(env_file.as_path(), config.user_home.as_path());
1157-
arguments.push(format!("'{}'", render_nu_path_ref(&relative)));
1166+
arguments.push(format!("'{}'", render_nu_path_ref(&home_relative)));
11581167
}
1159-
content.lines().any(|line| {
1168+
for line in content.lines() {
11601169
let line = line.trim_start();
11611170
let Some(argument) = line.strip_prefix(". ").or_else(|| line.strip_prefix("source "))
11621171
else {
1163-
return false;
1172+
continue;
11641173
};
1165-
arguments.iter().any(|expected| {
1166-
argument.trim_start().strip_prefix(expected).is_some_and(|rest| {
1174+
let argument = argument.trim_start();
1175+
if arguments.iter().any(|expected| {
1176+
argument.strip_prefix(expected).is_some_and(|rest| {
11671177
rest.is_empty()
11681178
|| rest.starts_with(char::is_whitespace)
11691179
|| rest.starts_with(';')
11701180
})
1171-
})
1172-
})
1173-
})
1181+
}) {
1182+
return true;
1183+
}
1184+
}
1185+
}
1186+
false
11741187
}
11751188

11761189
/// Print instructions for sourcing the environment files and adding bin to `PATH`.
@@ -1181,36 +1194,21 @@ fn print_path_instructions(env_dir: &vt_path::AbsolutePath) {
11811194
// SHELL describes the login shell, which may differ from this terminal.
11821195
let shell = env.vp_shell.as_deref().and_then(|s| s.parse().ok());
11831196
let commands = [
1184-
(Shell::Posix, "Bash/Zsh", "env"),
1185-
(Shell::Fish, "Fish", "env.fish"),
1186-
(Shell::NuShell, "Nushell", "env.nu"),
1187-
(Shell::PowerShell, "PowerShell", "env.ps1"),
1197+
(Shell::Posix, "Bash/Zsh", EnvShell::Posix),
1198+
(Shell::Fish, "Fish", EnvShell::Fish),
1199+
(Shell::NuShell, "Nushell", EnvShell::Nu),
1200+
(Shell::PowerShell, "PowerShell", EnvShell::Powershell),
11881201
];
11891202
if shell == Some(Shell::Cmd) {
11901203
// cmd has no sourceable environment file. A new terminal inherits the
11911204
// persistent PATH written by setup.
11921205
output::raw(" In cmd.exe, open a new terminal to load the updated PATH.");
11931206
} else {
1194-
for (kind, label, file) in commands {
1207+
for (kind, label, env_shell) in commands {
11951208
if shell.is_some_and(|s| s != kind) {
11961209
continue;
11971210
}
1198-
let path = env_dir.join(file).to_string();
1199-
let command = match kind {
1200-
Shell::Posix => {
1201-
format!(". \"{}\"", escape_posix_double_quoted_string(&path))
1202-
}
1203-
Shell::Fish => {
1204-
format!("source \"{}\"", escape_fish_double_quoted_string(&path))
1205-
}
1206-
Shell::NuShell => {
1207-
format!("source \"{}\"", escape_nu_double_quoted_string(&path))
1208-
}
1209-
Shell::PowerShell => {
1210-
format!(". '{}'", escape_powershell_single_quoted_string(&path))
1211-
}
1212-
Shell::Cmd => unreachable!(),
1213-
};
1211+
let command = env_shell.source_command(env_dir);
12141212
if shell.is_some() {
12151213
output::raw(&format!(" {command}"));
12161214
} else {
@@ -1219,9 +1217,12 @@ fn print_path_instructions(env_dir: &vt_path::AbsolutePath) {
12191217
}
12201218
}
12211219
output::raw("");
1222-
if (!cfg!(windows) || shell == Some(Shell::NuShell))
1223-
&& !matches!(shell, Some(Shell::Cmd | Shell::PowerShell))
1224-
{
1220+
let supports_profile_check = match shell {
1221+
Some(Shell::Cmd | Shell::PowerShell) => false,
1222+
Some(Shell::NuShell) => true,
1223+
_ => !cfg!(windows),
1224+
};
1225+
if supports_profile_check {
12251226
if has_configured_profile(&env) {
12261227
output::raw(" Or open a new terminal to load your configured shell profile.");
12271228
} else {

0 commit comments

Comments
 (0)