Skip to content

Commit d51567e

Browse files
authored
Merge branch 'main' into deps/upstream-update
2 parents 660fdda + 6ae8d49 commit d51567e

6 files changed

Lines changed: 117 additions & 12 deletions

File tree

crates/vite_global_cli/src/commands/global/install.rs

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
use std::{
44
collections::{HashMap, HashSet},
55
io::{IsTerminal, Read, Write},
6-
process::Stdio,
7-
time::Duration,
6+
process::{self, Stdio},
7+
time::{Duration, SystemTime, UNIX_EPOCH},
88
};
99

1010
use futures::{StreamExt, stream::FuturesUnordered};
@@ -338,12 +338,7 @@ pub async fn install(
338338

339339
// 4.5 Commit the install by discarding the backup and reporting the installed bins.
340340
if let Some(backup) = backup {
341-
if let Err(error) = backup.discard().await {
342-
if first_error.is_none() {
343-
first_error = Some(package_error(&package_name, error));
344-
}
345-
continue;
346-
}
341+
backup.discard().await;
347342
}
348343

349344
// 4.6 Print success message
@@ -461,8 +456,7 @@ impl PackageBackup {
461456
return Ok(None);
462457
}
463458

464-
let backup_dir = get_tmp_dir()?.join("packages").join(package_name);
465-
remove_dir_all_if_exists(&backup_dir).await?;
459+
let backup_dir = unique_backup_dir(package_name)?;
466460
if let Some(parent) = backup_dir.parent() {
467461
tokio::fs::create_dir_all(parent).await?;
468462
}
@@ -487,11 +481,31 @@ impl PackageBackup {
487481
Ok(())
488482
}
489483

490-
async fn discard(self) -> Result<(), Error> {
491-
remove_dir_all_if_exists(&self.backup_dir).await
484+
async fn discard(self) {
485+
if let Err(error) = remove_dir_all_if_exists(&self.backup_dir).await {
486+
tracing::warn!(
487+
"Failed to remove old global package backup at {}: {}",
488+
self.backup_dir.as_path().display(),
489+
error
490+
);
491+
}
492492
}
493493
}
494494

495+
fn unique_backup_dir(package_name: &str) -> Result<AbsolutePathBuf, Error> {
496+
let base = get_tmp_dir()?.join("packages").join(package_name);
497+
let package_dir_name =
498+
base.as_path().file_name().and_then(|name| name.to_str()).unwrap_or("package");
499+
let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_nanos();
500+
let backup_name = format!("{package_dir_name}.{}.{}.old", process::id(), timestamp);
501+
502+
let mut backup_path = base.as_path().to_path_buf();
503+
backup_path.set_file_name(backup_name);
504+
505+
AbsolutePathBuf::new(backup_path)
506+
.ok_or_else(|| Error::ConfigError("Invalid global package backup path".into()))
507+
}
508+
495509
async fn cleanup_failed_install(
496510
package_name: &str,
497511
backup: Option<PackageBackup>,
@@ -1022,6 +1036,48 @@ mod tests {
10221036
}
10231037
}
10241038

1039+
#[tokio::test]
1040+
async fn test_package_backup_uses_unique_tmp_dir_for_scoped_package() {
1041+
use tempfile::TempDir;
1042+
use vite_path::AbsolutePathBuf;
1043+
1044+
let temp_dir = TempDir::new().unwrap();
1045+
let temp_path = temp_dir.path().to_path_buf();
1046+
let _env_guard = vite_shared::EnvConfig::test_guard(
1047+
vite_shared::EnvConfig::for_test_with_home(&temp_path),
1048+
);
1049+
1050+
let package_dir =
1051+
AbsolutePathBuf::new(temp_path.join("packages").join("@scope").join("pkg")).unwrap();
1052+
tokio::fs::create_dir_all(&package_dir).await.unwrap();
1053+
tokio::fs::write(package_dir.join("marker").as_path(), "current").await.unwrap();
1054+
1055+
let stale_backup =
1056+
AbsolutePathBuf::new(temp_path.join("tmp").join("packages").join("@scope").join("pkg"))
1057+
.unwrap();
1058+
tokio::fs::create_dir_all(&stale_backup).await.unwrap();
1059+
tokio::fs::write(stale_backup.join("stale").as_path(), "locked").await.unwrap();
1060+
1061+
let backup = PackageBackup::create("@scope/pkg", &package_dir)
1062+
.await
1063+
.unwrap()
1064+
.expect("existing package should be backed up");
1065+
1066+
assert_ne!(backup.backup_dir.as_path(), stale_backup.as_path());
1067+
assert!(
1068+
stale_backup.join("stale").as_path().exists(),
1069+
"stale fixed backup should be left untouched"
1070+
);
1071+
assert!(
1072+
backup.backup_dir.join("marker").as_path().exists(),
1073+
"current package should be moved into the unique backup"
1074+
);
1075+
assert!(
1076+
!package_dir.as_path().exists(),
1077+
"original package directory should be moved out before reinstall"
1078+
);
1079+
}
1080+
10251081
#[test]
10261082
fn test_is_local_package_spec_relative_paths() {
10271083
assert!(is_local_package_spec("."));
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
22.22.0
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/usr/bin/env node
2+
console.log('env-install-stale-backup-cli');
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"name": "@scope/env-install-stale-backup-pkg",
3+
"version": "1.0.0",
4+
"bin": {
5+
"env-install-stale-backup-cli": "./cli.js"
6+
}
7+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
> vp install -g ./env-install-stale-backup-pkg # Install scoped package globally
2+
info: Installing 1 global package with Node.js <semver>
3+
✓ Installed @scope/env-install-stale-backup-pkg <semver>
4+
Bins: env-install-stale-backup-cli
5+
6+
> node -e "const fs = require('fs'); const path = require('path'); const cp = require('child_process'); const dir = path.join(process.env.VP_HOME, 'tmp/packages/@scope/env-install-stale-backup-pkg'); fs.mkdirSync(dir, { recursive: true }); const exe = path.join(dir, 'locked-node.exe'); fs.copyFileSync(process.execPath, exe); fs.writeFileSync(path.join(dir, 'stale.txt'), 'stale'); const child = cp.spawn(exe, ['-e', 'setTimeout(() => {}, 60000)'], { detached: true, stdio: 'ignore' }); child.unref(); fs.writeFileSync(path.join(dir, 'pid.txt'), String(child.pid));" # Seed stale locked fixed backup path
7+
> vp install -g ./env-install-stale-backup-pkg # Reinstall should ignore stale backup
8+
info: Installing 1 global package with Node.js <semver>
9+
✓ Installed @scope/env-install-stale-backup-pkg <semver>
10+
Bins: env-install-stale-backup-cli
11+
12+
> node -e "const fs = require('fs'); const path = require('path'); console.log(fs.readFileSync(path.join(process.env.VP_HOME, 'tmp/packages/@scope/env-install-stale-backup-pkg/stale.txt'), 'utf8'));" # Stale backup should be untouched
13+
stale
14+
15+
> node -e "const fs = require('fs'); const path = require('path'); console.log(fs.readFileSync(path.join(process.env.VP_HOME, 'bins/env-install-stale-backup-cli.json'), 'utf8'));" # Bin config should still point to package
16+
{
17+
"name": "env-install-stale-backup-cli",
18+
"package": "@scope/env-install-stale-backup-pkg",
19+
"version": "1.0.0",
20+
"nodeVersion": "22.22.0",
21+
"source": "vp"
22+
}
23+
24+
> node -e "const fs = require('fs'); const path = require('path'); const pidPath = path.join(process.env.VP_HOME, 'tmp/packages/@scope/env-install-stale-backup-pkg/pid.txt'); try { process.kill(Number(fs.readFileSync(pidPath, 'utf8')), 'SIGTERM'); } catch {}" # Stop stale backup lock process
25+
> vp remove -g @scope/env-install-stale-backup-pkg # Cleanup
26+
Uninstalled @scope/env-install-stale-backup-pkg
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"env": {},
3+
"ignoredPlatforms": ["linux", "darwin"],
4+
"commands": [
5+
"vp install -g ./env-install-stale-backup-pkg # Install scoped package globally",
6+
"node -e \"const fs = require('fs'); const path = require('path'); const cp = require('child_process'); const dir = path.join(process.env.VP_HOME, 'tmp/packages/@scope/env-install-stale-backup-pkg'); fs.mkdirSync(dir, { recursive: true }); const exe = path.join(dir, 'locked-node.exe'); fs.copyFileSync(process.execPath, exe); fs.writeFileSync(path.join(dir, 'stale.txt'), 'stale'); const child = cp.spawn(exe, ['-e', 'setTimeout(() => {}, 60000)'], { detached: true, stdio: 'ignore' }); child.unref(); fs.writeFileSync(path.join(dir, 'pid.txt'), String(child.pid));\" # Seed stale locked fixed backup path",
7+
"vp install -g ./env-install-stale-backup-pkg # Reinstall should ignore stale backup",
8+
"node -e \"const fs = require('fs'); const path = require('path'); console.log(fs.readFileSync(path.join(process.env.VP_HOME, 'tmp/packages/@scope/env-install-stale-backup-pkg/stale.txt'), 'utf8'));\" # Stale backup should be untouched",
9+
"node -e \"const fs = require('fs'); const path = require('path'); console.log(fs.readFileSync(path.join(process.env.VP_HOME, 'bins/env-install-stale-backup-cli.json'), 'utf8'));\" # Bin config should still point to package",
10+
"node -e \"const fs = require('fs'); const path = require('path'); const pidPath = path.join(process.env.VP_HOME, 'tmp/packages/@scope/env-install-stale-backup-pkg/pid.txt'); try { process.kill(Number(fs.readFileSync(pidPath, 'utf8')), 'SIGTERM'); } catch {}\" # Stop stale backup lock process",
11+
"vp remove -g @scope/env-install-stale-backup-pkg # Cleanup"
12+
]
13+
}

0 commit comments

Comments
 (0)