diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 881842b..6b9f61e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,9 +22,24 @@ jobs: with: save-cache: ${{ github.ref_name == 'main' }} - - run: cargo check + - uses: actions/setup-node@v4 + with: + node-version: lts/* + + - name: Prepare fixtures + run: | + corepack enable + cd fixtures/global-cache + yarn install + + - name: Build + run: cargo build + + - name: Check + run: cargo check - - run: cargo test + - name: Run tests + run: cargo test clippy: runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index eb5a316..1de5659 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -target +target \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index e720622..26189ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -144,12 +144,6 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" -[[package]] -name = "clean-path" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaa6b4b263a5d737e9bf6b7c09b72c41a5480aec4d7219af827f6564e950b6a5" - [[package]] name = "combine" version = "4.6.7" @@ -233,6 +227,27 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys", +] + [[package]] name = "either" version = "1.15.0" @@ -317,6 +332,17 @@ dependencies = [ "slab", ] +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "glob" version = "0.3.3" @@ -386,6 +412,16 @@ version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +[[package]] +name = "libredox" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" +dependencies = [ + "bitflags 2.9.3", + "libc", +] + [[package]] name = "log" version = "0.4.27" @@ -486,10 +522,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] -name = "path-slash" -version = "0.2.1" +name = "option-ext" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "pathdiff" @@ -542,13 +578,12 @@ name = "pnp" version = "0.12.2" dependencies = [ "byteorder", - "clean-path", "concurrent_lru", "criterion", + "dirs", "fancy-regex", "miniz_oxide", "mmap-rs", - "path-slash", "pathdiff", "radix_trie", "rstest", @@ -615,6 +650,17 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom", + "libredox", + "thiserror 2.0.16", +] + [[package]] name = "regex" version = "1.11.2" @@ -873,6 +919,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + [[package]] name = "wasm-bindgen" version = "0.2.100" diff --git a/Cargo.toml b/Cargo.toml index a5ccf8b..1705cd6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,18 +9,17 @@ repository = "https://github.com/yarnpkg/pnp-rs" [dependencies] byteorder = "1" -clean-path = "0.2.1" concurrent_lru = "^0.2" fancy-regex = { version = "^0.16.0", default-features = false, features = ["std"] } miniz_oxide = "^0.8" mmap-rs = { version = "^0.6", optional = true } -path-slash = "0.2.1" pathdiff = "^0.2" radix_trie = "0.2.1" serde = { version = "1", features = ["derive"] } serde_json = "1" thiserror = "2" rustc-hash = "2" +dirs = "6.0.0" [dev-dependencies] rstest = "0.26.0" diff --git a/fixtures/global-cache/.gitignore b/fixtures/global-cache/.gitignore new file mode 100644 index 0000000..5f58b7e --- /dev/null +++ b/fixtures/global-cache/.gitignore @@ -0,0 +1,2 @@ +.yarn/* +.pnp.* diff --git a/fixtures/global-cache/.yarnrc.yml b/fixtures/global-cache/.yarnrc.yml new file mode 100644 index 0000000..956fdce --- /dev/null +++ b/fixtures/global-cache/.yarnrc.yml @@ -0,0 +1,3 @@ +enableGlobalCache: true + +nodeLinker: pnp diff --git a/fixtures/global-cache/package.json b/fixtures/global-cache/package.json new file mode 100644 index 0000000..1be2eac --- /dev/null +++ b/fixtures/global-cache/package.json @@ -0,0 +1,7 @@ +{ + "name": "global-cache", + "dependencies": { + "source-map-support": "^0.5.21" + }, + "packageManager": "yarn@4.9.2" +} diff --git a/fixtures/global-cache/test.js b/fixtures/global-cache/test.js new file mode 100644 index 0000000..1e73bc0 --- /dev/null +++ b/fixtures/global-cache/test.js @@ -0,0 +1 @@ +console.log(require.resolve('source-map-support')); diff --git a/fixtures/global-cache/yarn.lock b/fixtures/global-cache/yarn.lock new file mode 100644 index 0000000..dd14adb --- /dev/null +++ b/fixtures/global-cache/yarn.lock @@ -0,0 +1,38 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10c0 + +"buffer-from@npm:^1.0.0": + version: 1.1.2 + resolution: "buffer-from@npm:1.1.2" + checksum: 10c0/124fff9d66d691a86d3b062eff4663fe437a9d9ee4b47b1b9e97f5a5d14f6d5399345db80f796827be7c95e70a8e765dd404b7c3ff3b3324f98e9b0c8826cc34 + languageName: node + linkType: hard + +"global-cache@workspace:.": + version: 0.0.0-use.local + resolution: "global-cache@workspace:." + dependencies: + source-map-support: "npm:^0.5.21" + languageName: unknown + linkType: soft + +"source-map-support@npm:^0.5.21": + version: 0.5.21 + resolution: "source-map-support@npm:0.5.21" + dependencies: + buffer-from: "npm:^1.0.0" + source-map: "npm:^0.6.0" + checksum: 10c0/9ee09942f415e0f721d6daad3917ec1516af746a8120bba7bb56278707a37f1eb8642bde456e98454b8a885023af81a16e646869975f06afc1a711fb90484e7d + languageName: node + linkType: hard + +"source-map@npm:^0.6.0": + version: 0.6.1 + resolution: "source-map@npm:0.6.1" + checksum: 10c0/ab55398007c5e5532957cb0beee2368529618ac0ab372d789806f5718123cc4367d57de3904b4e6a4170eb5a0b0f41373066d02ca0735a0c4d75c7d328d3e011 + languageName: node + linkType: hard diff --git a/src/lib.rs b/src/lib.rs index e014b98..08decf9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -161,7 +161,6 @@ pub fn init_pnp_manifest(manifest: &mut Manifest, p: &Path) { for (name, ranges) in manifest.package_registry_data.iter_mut() { for (reference, info) in ranges.iter_mut() { let package_location = manifest.manifest_dir.join(info.package_location.clone()); - let normalized_location = util::normalize_path(package_location.to_string_lossy()); info.package_location = PathBuf::from(normalized_location); @@ -208,6 +207,8 @@ pub fn find_locator<'a>(manifest: &'a Manifest, path: &Path) -> Option<&'a Packa } } + let path = util::normalize_path(path.to_string_lossy()); + manifest.location_trie.get_ancestor_value(&path) } diff --git a/src/lib_tests.rs b/src/lib_tests.rs index b64e30c..cff7390 100644 --- a/src/lib_tests.rs +++ b/src/lib_tests.rs @@ -20,12 +20,12 @@ struct TestSuite { #[cfg(test)] mod tests { - use std::{fs, path::PathBuf}; + use std::{env, fs, path::PathBuf}; use super::*; use crate::{ ResolutionConfig, ResolutionHost, init_pnp_manifest, load_pnp_manifest, - parse_bare_identifier, resolve_to_unqualified, resolve_to_unqualified_via_manifest, + parse_bare_identifier, resolve_to_unqualified, resolve_to_unqualified_via_manifest, util, }; #[test] @@ -162,4 +162,55 @@ mod tests { let parsed = parse_bare_identifier("@scope/pkg/a/b/c/index.js"); assert_eq!(parsed, Ok(("@scope/pkg".to_string(), Some("a/b/c/index.js".to_string())))); } + + #[test] + fn test_global_cache() { + let manifest = load_pnp_manifest( + env::current_dir() + .unwrap() + .join("fixtures") + .join("global-cache") + .join(".pnp.cjs") + .as_path(), + ) + .unwrap(); + + let home_dir = dirs::home_dir().unwrap(); + + #[cfg(windows)] + let global_cache = home_dir.join("AppData\\Local\\Yarn\\Berry\\cache"); + #[cfg(not(windows))] + let global_cache = home_dir.join(".yarn/berry/cache"); + + let result = resolve_to_unqualified_via_manifest( + &manifest, + "source-map", + global_cache + .join("source-map-support-npm-0.5.21-09ca99e250-10c0.zip") + .join("node_modules") + .join("source-map-support") + .join("") + .as_path(), + ); + + match result { + Ok(Resolution::Resolved(path, subpath)) => { + assert_eq!( + path.to_string_lossy(), + util::normalize_path( + global_cache + .join("source-map-npm-0.6.1-1a3621db16-10c0.zip") + .join("node_modules") + .join("source-map") + .join("") + .to_string_lossy() + ) + ); + assert_eq!(subpath, None); + } + _ => { + panic!("Unexpected resolve failed"); + } + } + } } diff --git a/src/util.rs b/src/util.rs index ec9a7b9..41ff13a 100644 --- a/src/util.rs +++ b/src/util.rs @@ -2,8 +2,7 @@ use fancy_regex::Regex; use serde::{Deserialize, Deserializer, de::Error}; use std::borrow::Cow; -use path_slash::PathBufExt; -use std::path::{Path, PathBuf}; +use std::path::{MAIN_SEPARATOR_STR, Path, PathBuf}; #[derive(Debug, Default, Clone)] pub struct Trie { @@ -36,10 +35,54 @@ impl Trie { pub fn normalize_path>(original: P) -> String { let original_str = original.as_ref(); - let p = PathBuf::from(original_str); - let mut str = clean_path::clean(p).to_slash_lossy().to_string(); + let check_str_root = original_str.strip_prefix("/"); + let str_minus_root = check_str_root.unwrap_or(original_str); - if original_str.ends_with('/') && !str.ends_with('/') { + let components = str_minus_root.split(&['/', '\\'][..]); + + let mut out: Vec<&str> = Vec::new(); + + for comp in components { + match comp { + "" | "." => { + // Those components don't progress the path + } + + ".." => match out.last() { + None if check_str_root.is_some() => { + // No need to add a ".." since we're already at the root + } + + Some(&"..") | None => { + out.push(comp); + } + + Some(_) => { + out.pop(); + } + }, + + comp => out.push(comp), + } + } + + if check_str_root.is_some() { + if out.is_empty() { + return "/".to_string(); + } else { + out.insert(0, ""); + } + } + + let mut str = out.join("/"); + + if out.is_empty() { + return ".".to_string(); + } + + if (original_str.ends_with('/') || original_str.ends_with(MAIN_SEPARATOR_STR)) + && !str.ends_with('/') + { str.push('/'); } @@ -59,12 +102,17 @@ mod tests { assert_eq!(normalize_path("foo//bar"), "foo/bar"); assert_eq!(normalize_path("foo/./bar"), "foo/bar"); assert_eq!(normalize_path("foo/../bar"), "bar"); + assert_eq!(normalize_path("foo/..//bar"), "bar"); assert_eq!(normalize_path("foo/bar/.."), "foo"); assert_eq!(normalize_path("foo/../../bar"), "../bar"); assert_eq!(normalize_path("../foo/../../bar"), "../../bar"); assert_eq!(normalize_path("./foo"), "foo"); assert_eq!(normalize_path("../foo"), "../foo"); + assert_eq!(normalize_path("../D:/foo"), "../D:/foo"); assert_eq!(normalize_path("/foo/bar"), "/foo/bar"); + assert_eq!(normalize_path("/foo/../../bar/baz"), "/bar/baz"); + assert_eq!(normalize_path("/../foo/bar"), "/foo/bar"); + assert_eq!(normalize_path("/../foo/bar//"), "/foo/bar/"); assert_eq!(normalize_path("/foo/bar/"), "/foo/bar/"); } }