Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,19 @@ Uninstalled conflict-pkg

Install with specific Node.js version

**Exit code:** 1

```
VITE+ - The Unified Toolchain for the Web

info: Installing 1 global package with Node.js <version>
error: Failed to install conflict-pkg: Package was not installed correctly, package.json not found at <home>/.vite-plus/packages/conflict-pkg#<uuid>/lib/node_modules/conflict-pkg/package.json

@liangmiQwQ liangmiQwQ Jul 23, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This problem seems already exposed after snap test migration (Node 20 / npm 10, environment changed) but it doesn't seems noticed.

warn: Package 'conflict-pkg' provides 'node' binary, but it conflicts with a built-in shim. Skipping.
✓ Installed conflict-pkg 1.0.0
Bins: conflict-cli
```

## `vp remove -g conflict-pkg`

Cleanup

**Exit code:** 1

```
Failed to uninstall conflict-pkg: Package conflict-pkg is not installed
Uninstalled conflict-pkg
```
Original file line number Diff line number Diff line change
Expand Up @@ -32,51 +32,26 @@ Uninstalled command-env-install-node-version-pkg

Install with Node.js 20

**Exit code:** 1

```
VITE+ - The Unified Toolchain for the Web

info: Installing 1 global package with Node.js <version>
error: Failed to install command-env-install-node-version-pkg: Package was not installed correctly, package.json not found at <home>/.vite-plus/packages/command-env-install-node-version-pkg#<uuid>/lib/node_modules/command-env-install-node-version-pkg/package.json
✓ Installed command-env-install-node-version-pkg 1.0.0
Bins: command-env-install-node-version-pkg-cli
```

## `node -e 'const d=JSON.parse(require('\''fs'\'').readFileSync(process.env.VP_HOME+'\''/bins/command-env-install-node-version-pkg-cli.json'\'','\''utf8'\'')); console.log('\''Node major:'\'', d.nodeVersion.split('\''.'\'')[0])'`

Verify Node 20

**Exit code:** 1

```
node:fs:441
return binding.readFileUtf8(path, stringToFlags(options.flag));
^

Error: ENOENT: no such file or directory, open '<home>/.vite-plus/bins/command-env-install-node-version-pkg-cli.json'
at Object.readFileSync (node:fs:441:20)
at [eval]:1:34
at runScriptInThisContext (node:internal/vm:219:10)
at node:internal/process/execution:451:12
at [eval]-wrapper:6:24
at runScriptInContext (node:internal/process/execution:449:60)
at evalFunction (node:internal/process/execution:283:30)
at evalTypeScript (node:internal/process/execution:295:3)
at node:internal/main/eval_string:71:3 {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '<home>/.vite-plus/bins/command-env-install-node-version-pkg-cli.json'
}

Node.js <version>
Node major: 20
```

## `vp remove -g command-env-install-node-version-pkg`

Cleanup

**Exit code:** 1

```
Failed to uninstall command-env-install-node-version-pkg: Package command-env-install-node-version-pkg is not installed
Uninstalled command-env-install-node-version-pkg
```
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ Global package - shows binary path with metadata
```
VITE+ - The Unified Toolchain for the Web

<home>/.vite-plus/packages/cowsay#<uuid>/lib/node_modules/cowsay/./cli.js
<home>/.vite-plus/packages/cowsay/<uuid>/lib/node_modules/cowsay/./cli.js
Package: cowsay@1.6.0
Binaries: cowsay, cowthink
Node: <version>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ should support global json output
"wanted": "1.0.1",
"latest": "1.0.1",
"dependent": "global",
"location": "<home>/.vite-plus/packages/testnpm2#<uuid>/lib/node_modules/testnpm2"
"location": "<home>/.vite-plus/packages/testnpm2/<uuid>/lib/node_modules/testnpm2"
}
}
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,24 @@ const packageBase = 'long-time-install-package';
const scopeDir = path.join(process.env.VP_HOME, 'packages', '@scope');
const metadataPath = path.join(scopeDir, `${packageBase}.json`);
const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
const activeDir = `${packageBase}${metadata.installId}`;
const packageDir = path.join(scopeDir, packageBase);
const activeDir = metadata.installId;
const expectStale = process.argv.includes('--expect-stale');

const packageDirs = fs
.readdirSync(scopeDir, { withFileTypes: true })
.readdirSync(packageDir, { withFileTypes: true })
.filter((entry) => {
if (!entry.isDirectory()) {
return false;
}
if (entry.name === packageBase) {
return true;
}
return /^long-time-install-package#[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(
entry.name,
return (
entry.isDirectory()
&& /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(
entry.name,
)
);
})
.map((entry) => entry.name)
.sort();

const hasIdentifiedStale = packageDirs.some((name) => name !== packageBase && name !== activeDir);
const hasIdentifiedStale = packageDirs.some((name) => name !== activeDir);

console.log(
hasIdentifiedStale ? 'interrupted stale package exists' : 'interrupted stale package removed',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env node

import { dirname, join } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';

const loadedModule = join(dirname(fileURLToPath(import.meta.url)), 'loaded.js');
// Keep `#` unescaped to reproduce the old install layout's URL-fragment failure.
const loadedModuleUrl = pathToFileURL(loadedModule).href.replaceAll('%23', '#');
await import(loadedModuleUrl);
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log('global dynamic import loaded');
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "global-install-dynamic-import",
"version": "0.0.0",
"type": "module",
"bin": {
"global-install-dynamic-import": "./bin.js"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[[case]]
name = "global_install_dynamic_import"
vp = "global"
comment = "Regression test for #2220: globally installed packages can dynamically import an absolute path inside their installation."
steps = [
{ argv = ["vp", "install", "-g", "."] },
{ argv = ["global-install-dynamic-import"] },
]
after = [
{ argv = ["vp", "remove", "-g", "global-install-dynamic-import"], continue-on-failure = true },
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# global_install_dynamic_import

Regression test for #2220: globally installed packages can dynamically import an absolute path inside their installation.

## `vp install -g .`

```
VITE+ - The Unified Toolchain for the Web

info: Installing 1 global package with Node.js <version>
✓ Installed global-install-dynamic-import 0.0.0
Bins: global-install-dynamic-import
```

## `global-install-dynamic-import`

```
global dynamic import loaded
```
45 changes: 30 additions & 15 deletions crates/vite_global_cli/src/commands/env/package_metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,21 @@ use vite_path::AbsolutePathBuf;
use super::config::get_packages_dir;
use crate::error::Error;

// `#` is filesystem-safe but invalid in npm package names, so sibling installs cannot collide.
pub(crate) const INSTALL_ID_PREFIX: char = '#';
// Keeps npm's 214-byte maximum package name within the common 255-byte filename limit.
pub(crate) const INSTALL_ID_LENGTH: usize = 37;
// This is legacy, for old Vite+ version's compatibility
const LEGACY_INSTALL_ID_PREFIX: char = '#';
const INSTALL_ID_LENGTH: usize = 36;

pub(crate) fn is_install_id(value: &str) -> bool {
pub(crate) fn is_nested_install_id(value: &str) -> bool {
value.len() == INSTALL_ID_LENGTH
&& value
.strip_prefix(INSTALL_ID_PREFIX)
.and_then(|uuid| Uuid::parse_str(uuid).ok())
&& Uuid::parse_str(value)
.ok()
.is_some_and(|uuid| uuid.get_version() == Some(Version::Random))
}

pub(crate) fn is_legacy_install_id(value: &str) -> bool {
value.strip_prefix(LEGACY_INSTALL_ID_PREFIX).is_some_and(is_nested_install_id)
}

/// Metadata for a globally installed package.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
Expand All @@ -31,7 +33,7 @@ pub struct PackageMetadata {
pub name: String,
/// Package version
pub version: String,
/// Directory identifier for this installation. Empty for legacy installs.
/// Directory identifier for this installation. Empty or `#`-prefixed for legacy installs.
#[serde(default)]
pub install_id: String,
/// Platform versions used during installation
Expand Down Expand Up @@ -96,15 +98,18 @@ impl PackageMetadata {
Self::installation_dir_for(&self.name, &self.install_id)
}

/// Resolve an installation prefix, including the legacy empty-ID layout.
/// Resolve an installation prefix, including both legacy layouts.
pub fn installation_dir_for(
package_name: &str,
install_id: &str,
) -> Result<AbsolutePathBuf, Error> {
let packages_dir = get_packages_dir()?;
let package_dir = packages_dir.join(package_name);
if install_id.is_empty() {
Ok(packages_dir.join(package_name))
} else if is_install_id(install_id) {
Ok(package_dir)
} else if is_nested_install_id(install_id) {
Ok(package_dir.join(install_id))
Comment thread
liangmiQwQ marked this conversation as resolved.
} else if is_legacy_install_id(install_id) {
Ok(packages_dir.join(format!("{package_name}{install_id}")))
} else {
Err(Error::ConfigError(
Expand Down Expand Up @@ -241,7 +246,7 @@ mod tests {
}

#[test]
fn test_installation_dir_uses_install_id() {
fn test_installation_dir_supports_current_and_legacy_layouts() {
use tempfile::TempDir;

let temp_dir = TempDir::new().unwrap();
Expand All @@ -250,18 +255,28 @@ mod tests {
);

let legacy = PackageMetadata::installation_dir_for("@scope/pkg", "").unwrap();
let identified = PackageMetadata::installation_dir_for(
let legacy_identified = PackageMetadata::installation_dir_for(
"@scope/pkg",
"#123e4567-e89b-42d3-a456-426614174000",
)
.unwrap();
let identified = PackageMetadata::installation_dir_for(
"@scope/pkg",
"987e6543-e21b-42d3-a456-426614174000",
)
.unwrap();

assert!(legacy.as_path().ends_with("packages/@scope/pkg"));
assert!(
identified
legacy_identified
.as_path()
.ends_with("packages/@scope/pkg#123e4567-e89b-42d3-a456-426614174000")
);
assert!(
identified
.as_path()
.ends_with("packages/@scope/pkg/987e6543-e21b-42d3-a456-426614174000")
);
assert!(PackageMetadata::installation_dir_for("@scope/pkg", "invalid").is_err());
}

Expand Down
Loading
Loading