pnp 0.12.9

Resolution primitives for Yarn PnP
Documentation
use std::path::Path;

use serde::Deserialize;

use crate::{Manifest, Resolution};

#[derive(Deserialize)]
struct Test {
    it: String,
    imported: String,
    importer: String,
    expected: String,
}

#[derive(Deserialize)]
struct TestSuite {
    manifest: Manifest,
    tests: Vec<Test>,
}

#[cfg(test)]
mod tests {
    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, util,
    };

    #[test]
    fn example() {
        let manifest = load_pnp_manifest(Path::new("data/pnp-yarn-v3.cjs")).unwrap();

        let host =
            ResolutionHost { find_pnp_manifest: Box::new(move |_| Ok(Some(manifest.clone()))) };

        let config = ResolutionConfig { host };

        let resolution =
            resolve_to_unqualified("lodash/cloneDeep", Path::new("/path/to/file"), &config);

        match resolution {
            Ok(Resolution::Resolved(_path, _subpath)) => {
                // path = "/path/to/lodash.zip"
                // subpath = "cloneDeep"
            }
            Ok(Resolution::Skipped) => {
                // This is returned when the PnP resolver decides that it shouldn't
                // handle the resolution for this particular specifier. In that case,
                // the specifier should be forwarded to the default resolver.
            }
            Err(_err) => {
                // An error happened during the resolution. Falling back to the default
                // resolver isn't recommended.
            }
        };
    }

    #[test]
    fn test_load_pnp_manifest() {
        load_pnp_manifest(Path::new("data/pnp-yarn-v3.cjs"))
            .expect("Assertion failed: Expected to load the .pnp.cjs file generated by Yarn 3");

        load_pnp_manifest(Path::new("data/pnp-yarn-v4.cjs"))
            .expect("Assertion failed: Expected to load the .pnp.cjs file generated by Yarn 4");
    }

    #[test]
    fn test_resolve_unqualified() {
        let expectations_path = std::env::current_dir()
            .expect("Assertion failed: Expected a valid current working directory")
            .join("data/test-expectations.json");

        let manifest_content = fs::read_to_string(&expectations_path)
            .expect("Assertion failed: Expected the expectations to be found");

        let mut test_suites: Vec<TestSuite> = serde_json::from_str(&manifest_content)
            .expect("Assertion failed: Expected the expectations to be loaded");

        for test_suite in test_suites.iter_mut() {
            let manifest = &mut test_suite.manifest;
            init_pnp_manifest(manifest, Path::new("/path/to/project/.pnp.cjs"));

            for test in test_suite.tests.iter() {
                let specifier = &test.imported;
                let parent = &PathBuf::from(&test.importer).join("fooo");

                let manifest_copy = manifest.clone();

                let host = ResolutionHost {
                    find_pnp_manifest: Box::new(move |_| Ok(Some(manifest_copy.clone()))),
                };

                let config = ResolutionConfig { host };

                let resolution = resolve_to_unqualified(specifier, parent, &config);

                match resolution {
                    Ok(Resolution::Resolved(path, _subpath)) => {
                        assert_eq!(path.to_string_lossy(), test.expected, "{}", test.it);
                    }
                    Ok(Resolution::Skipped) => {
                        assert_eq!(specifier, &test.expected, "{}", test.it);
                    }
                    Err(err) => {
                        assert_eq!(test.expected, "error!", "{}: {err}", test.it);
                    }
                }
            }
        }
    }

    #[test]
    fn test_edge_case_one_pkg_cached_and_unplugged() {
        let manifest = {
            let manifest_json_path =
                std::env::current_dir().unwrap().join("./data/edge_case_manifest_state.json");
            let manifest_content = fs::read_to_string(&manifest_json_path).unwrap();
            let mut manifest = serde_json::from_str::<Manifest>(&manifest_content).unwrap();
            init_pnp_manifest(&mut manifest, &manifest_json_path);
            manifest
        };

        let issuer = std::env::current_dir().unwrap().
            join("data/.yarn/unplugged/@carbon-icons-react-virtual-379302d360/node_modules/@carbon/icons-react/es/");

        let resolution =
            resolve_to_unqualified_via_manifest(&manifest, "@carbon/icon-helpers", &issuer)
                .unwrap();

        match resolution {
            Resolution::Resolved(resolved, _) => {
                assert!(resolved.ends_with(".yarn/unplugged/@carbon-icon-helpers-npm-10.54.0-a58f8b7b6c/node_modules/@carbon/icon-helpers"))
            }
            _ => {
                panic!("Unexpected resolve failed");
            }
        }
    }

    #[test]
    fn test_parse_single_package_name() {
        let parsed = parse_bare_identifier("pkg");
        assert_eq!(parsed, Ok(("pkg".to_string(), None)));
    }

    #[test]
    fn test_parse_scoped_package_name() {
        let parsed = parse_bare_identifier("@scope/pkg");
        assert_eq!(parsed, Ok(("@scope/pkg".to_string(), None)));
    }

    #[test]
    fn test_parse_package_name_with_long_subpath() {
        let parsed = parse_bare_identifier("pkg/a/b/c/index.js");
        assert_eq!(parsed, Ok(("pkg".to_string(), Some("a/b/c/index.js".to_string()))));
    }

    #[test]
    fn test_parse_scoped_package_with_long_subpath() {
        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_next::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");
            }
        }
    }

    #[test]
    fn test_preserve_package_registry_data_order() {
        let base_path = std::env::current_dir().unwrap().join("data");
        let manifest =
            load_pnp_manifest(base_path.join("pnp-yarn-v4-registry-data-order.cjs").as_path())
                .unwrap();

        let result = resolve_to_unqualified_via_manifest(
            &manifest,
            "inner-package",
            base_path.join(".yarn/unplugged/lib-virtual-35bde7b160/node_modules/lib").as_path(),
        );

        match result {
            Ok(Resolution::Resolved(path, subpath)) => {
                assert_eq!(
                    path.to_string_lossy(),
                    util::normalize_path(
                        base_path
                            .join("path")
                            .join("to")
                            .join("inner-package")
                            .join("")
                            .to_string_lossy()
                    )
                );
                assert_eq!(subpath, None);
            }
            _ => {
                panic!("Unexpected resolve failed");
            }
        }
    }
}