From cbb3f854332c348bb253e1284f7dcd7287bdf28d Mon Sep 17 00:00:00 2001 From: David Sherret Date: Tue, 8 Nov 2022 14:17:24 -0500 Subject: feat(unstable/npm): support peer dependencies (#16561) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds support for peer dependencies in npm packages. 1. If not found higher in the tree (ancestor and ancestor siblings), peer dependencies are resolved like a dependency similar to npm 7. 2. Optional peer dependencies are only resolved if found higher in the tree. 3. This creates "copy packages" or duplicates of a package when a package has different resolution due to peer dependency resolution—see https://pnpm.io/how-peers-are-resolved. Unlike pnpm though, duplicates of packages will have `_1`, `_2`, etc. added to the end of the package version in the directory in order to minimize the chance of hitting the max file path limit on Windows. This is done for both the local "node_modules" directory and also the global npm cache. The files are hard linked in this case to reduce hard drive space. This is a first pass and the code is definitely more inefficient than it could be. Closes #15823 --- cli/tests/integration/npm_tests.rs | 178 ++++++++++++++++++++- .../npm/peer_deps_with_copied_folders/main.out | 10 ++ .../npm/peer_deps_with_copied_folders/main.ts | 5 + .../peer_deps_with_copied_folders/main_info.out | 14 ++ .../main_info_json.out | 95 +++++++++++ .../@denotest/peer-dep-test-child/1.0.0/index.js | 1 + .../peer-dep-test-child/1.0.0/package.json | 8 + .../@denotest/peer-dep-test-child/2.0.0/index.js | 1 + .../peer-dep-test-child/2.0.0/package.json | 8 + .../peer-dep-test-grandchild/1.0.0/dist/index.js | 1 + .../peer-dep-test-grandchild/1.0.0/index.js | 1 + .../peer-dep-test-grandchild/1.0.0/package.json | 7 + .../@denotest/peer-dep-test-peer/1.0.0/index.js | 1 + .../peer-dep-test-peer/1.0.0/package.json | 4 + .../@denotest/peer-dep-test-peer/2.0.0/index.js | 1 + .../peer-dep-test-peer/2.0.0/package.json | 4 + 16 files changed, 338 insertions(+), 1 deletion(-) create mode 100644 cli/tests/testdata/npm/peer_deps_with_copied_folders/main.out create mode 100644 cli/tests/testdata/npm/peer_deps_with_copied_folders/main.ts create mode 100644 cli/tests/testdata/npm/peer_deps_with_copied_folders/main_info.out create mode 100644 cli/tests/testdata/npm/peer_deps_with_copied_folders/main_info_json.out create mode 100644 cli/tests/testdata/npm/registry/@denotest/peer-dep-test-child/1.0.0/index.js create mode 100644 cli/tests/testdata/npm/registry/@denotest/peer-dep-test-child/1.0.0/package.json create mode 100644 cli/tests/testdata/npm/registry/@denotest/peer-dep-test-child/2.0.0/index.js create mode 100644 cli/tests/testdata/npm/registry/@denotest/peer-dep-test-child/2.0.0/package.json create mode 100644 cli/tests/testdata/npm/registry/@denotest/peer-dep-test-grandchild/1.0.0/dist/index.js create mode 100644 cli/tests/testdata/npm/registry/@denotest/peer-dep-test-grandchild/1.0.0/index.js create mode 100644 cli/tests/testdata/npm/registry/@denotest/peer-dep-test-grandchild/1.0.0/package.json create mode 100644 cli/tests/testdata/npm/registry/@denotest/peer-dep-test-peer/1.0.0/index.js create mode 100644 cli/tests/testdata/npm/registry/@denotest/peer-dep-test-peer/1.0.0/package.json create mode 100644 cli/tests/testdata/npm/registry/@denotest/peer-dep-test-peer/2.0.0/index.js create mode 100644 cli/tests/testdata/npm/registry/@denotest/peer-dep-test-peer/2.0.0/package.json (limited to 'cli/tests') diff --git a/cli/tests/integration/npm_tests.rs b/cli/tests/integration/npm_tests.rs index 5dae2fd1c..87e853850 100644 --- a/cli/tests/integration/npm_tests.rs +++ b/cli/tests/integration/npm_tests.rs @@ -1002,7 +1002,7 @@ fn lock_file_missing_top_level_package() { let stderr = String::from_utf8(output.stderr).unwrap(); assert_eq!( stderr, - "error: the lockfile (deno.lock) is corrupt. You can recreate it with --lock-write\n" + "error: failed reading lockfile 'deno.lock'\n\nCaused by:\n the lockfile is corrupt. You can recreate it with --lock-write\n" ); } @@ -1054,6 +1054,182 @@ fn auto_discover_lock_file() { )); } +#[test] +fn peer_deps_with_copied_folders_and_lockfile() { + let _server = http_server(); + + let deno_dir = util::new_deno_dir(); + let temp_dir = util::TempDir::new(); + + // write empty config file + temp_dir.write("deno.json", "{}"); + let test_folder_path = test_util::testdata_path() + .join("npm") + .join("peer_deps_with_copied_folders"); + let main_contents = + std::fs::read_to_string(test_folder_path.join("main.ts")).unwrap(); + temp_dir.write("./main.ts", main_contents); + + let deno = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(temp_dir.path()) + .arg("run") + .arg("--unstable") + .arg("-A") + .arg("main.ts") + .envs(env_vars()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + assert!(output.status.success()); + + let expected_output = + std::fs::read_to_string(test_folder_path.join("main.out")).unwrap(); + + assert_eq!(String::from_utf8(output.stderr).unwrap(), expected_output); + + assert!(temp_dir.path().join("deno.lock").exists()); + let grandchild_path = deno_dir + .path() + .join("npm") + .join("localhost_4545") + .join("npm") + .join("registry") + .join("@denotest") + .join("peer-dep-test-grandchild"); + assert!(grandchild_path.join("1.0.0").exists()); + assert!(grandchild_path.join("1.0.0_1").exists()); // copy folder, which is hardlinked + + // run again + let deno = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(temp_dir.path()) + .arg("run") + .arg("--unstable") + .arg("-A") + .arg("main.ts") + .envs(env_vars()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + assert_eq!(String::from_utf8(output.stderr).unwrap(), "1\n2\n"); + assert!(output.status.success()); + + let deno = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(temp_dir.path()) + .arg("run") + .arg("--unstable") + .arg("--reload") + .arg("-A") + .arg("main.ts") + .envs(env_vars()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + assert_eq!(String::from_utf8(output.stderr).unwrap(), expected_output); + assert!(output.status.success()); + + // now run with local node modules + let deno = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(temp_dir.path()) + .arg("run") + .arg("--unstable") + .arg("--node-modules-dir") + .arg("-A") + .arg("main.ts") + .envs(env_vars()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + assert_eq!(String::from_utf8(output.stderr).unwrap(), "1\n2\n"); + assert!(output.status.success()); + + let deno_folder = temp_dir.path().join("node_modules").join(".deno"); + assert!(deno_folder + .join("@denotest+peer-dep-test-grandchild@1.0.0") + .exists()); + assert!(deno_folder + .join("@denotest+peer-dep-test-grandchild@1.0.0_1") + .exists()); // copy folder + + // now again run with local node modules + let deno = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(temp_dir.path()) + .arg("run") + .arg("--unstable") + .arg("--node-modules-dir") + .arg("-A") + .arg("main.ts") + .envs(env_vars()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + assert!(output.status.success()); + assert_eq!(String::from_utf8(output.stderr).unwrap(), "1\n2\n"); + + // now ensure it works with reloading + let deno = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(temp_dir.path()) + .arg("run") + .arg("--unstable") + .arg("--node-modules-dir") + .arg("--reload") + .arg("-A") + .arg("main.ts") + .envs(env_vars()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + assert!(output.status.success()); + assert_eq!(String::from_utf8(output.stderr).unwrap(), expected_output); + + // now ensure it works with reloading and no lockfile + let deno = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(temp_dir.path()) + .arg("run") + .arg("--unstable") + .arg("--node-modules-dir") + .arg("--no-lock") + .arg("--reload") + .arg("-A") + .arg("main.ts") + .envs(env_vars()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + assert_eq!(String::from_utf8(output.stderr).unwrap(), expected_output,); + assert!(output.status.success()); +} + +itest!(info_peer_deps { + args: "info --quiet --unstable npm/peer_deps_with_copied_folders/main.ts", + output: "npm/peer_deps_with_copied_folders/main_info.out", + exit_code: 0, + envs: env_vars(), + http_server: true, +}); + +itest!(info_peer_deps_json { + args: + "info --quiet --unstable --json npm/peer_deps_with_copied_folders/main.ts", + output: "npm/peer_deps_with_copied_folders/main_info_json.out", + exit_code: 0, + envs: env_vars(), + http_server: true, +}); + fn env_vars_no_sync_download() -> Vec<(String, String)> { vec![ ("DENO_NODE_COMPAT_URL".to_string(), util::std_file_url()), diff --git a/cli/tests/testdata/npm/peer_deps_with_copied_folders/main.out b/cli/tests/testdata/npm/peer_deps_with_copied_folders/main.out new file mode 100644 index 000000000..ce0dc6896 --- /dev/null +++ b/cli/tests/testdata/npm/peer_deps_with_copied_folders/main.out @@ -0,0 +1,10 @@ +Download http://localhost:4545/npm/registry/@denotest/peer-dep-test-child +Download http://localhost:4545/npm/registry/@denotest/peer-dep-test-grandchild +Download http://localhost:4545/npm/registry/@denotest/peer-dep-test-peer +Download http://localhost:4545/npm/registry/@denotest/peer-dep-test-child/1.0.0.tgz +Download http://localhost:4545/npm/registry/@denotest/peer-dep-test-child/2.0.0.tgz +Download http://localhost:4545/npm/registry/@denotest/peer-dep-test-grandchild/1.0.0.tgz +Download http://localhost:4545/npm/registry/@denotest/peer-dep-test-peer/1.0.0.tgz +Download http://localhost:4545/npm/registry/@denotest/peer-dep-test-peer/2.0.0.tgz +1 +2 diff --git a/cli/tests/testdata/npm/peer_deps_with_copied_folders/main.ts b/cli/tests/testdata/npm/peer_deps_with_copied_folders/main.ts new file mode 100644 index 000000000..a8ea8104a --- /dev/null +++ b/cli/tests/testdata/npm/peer_deps_with_copied_folders/main.ts @@ -0,0 +1,5 @@ +import version1 from "npm:@denotest/peer-dep-test-child@1"; +import version2 from "npm:@denotest/peer-dep-test-child@2"; + +console.error(version1); +console.error(version2); diff --git a/cli/tests/testdata/npm/peer_deps_with_copied_folders/main_info.out b/cli/tests/testdata/npm/peer_deps_with_copied_folders/main_info.out new file mode 100644 index 000000000..c9c4a59c1 --- /dev/null +++ b/cli/tests/testdata/npm/peer_deps_with_copied_folders/main_info.out @@ -0,0 +1,14 @@ +local: [WILDCARD]main.ts +type: TypeScript +dependencies: 6 unique +size: [WILDCARD] + +file:///[WILDCARD]/testdata/npm/peer_deps_with_copied_folders/main.ts (171B) +├─┬ npm:@denotest/peer-dep-test-child@1 - 1.0.0 ([WILDCARD]) +│ ├─┬ npm:@denotest/peer-dep-test-grandchild@1.0.0_@denotest+peer-dep-test-peer@1.0.0 ([WILDCARD]) +│ │ └── npm:@denotest/peer-dep-test-peer@1.0.0 ([WILDCARD]) +│ └── npm:@denotest/peer-dep-test-peer@1.0.0 ([WILDCARD]) +└─┬ npm:@denotest/peer-dep-test-child@2 - 2.0.0 ([WILDCARD]) + ├─┬ npm:@denotest/peer-dep-test-grandchild@1.0.0_@denotest+peer-dep-test-peer@2.0.0 ([WILDCARD]) + │ └── npm:@denotest/peer-dep-test-peer@2.0.0 ([WILDCARD]) + └── npm:@denotest/peer-dep-test-peer@2.0.0 ([WILDCARD]) diff --git a/cli/tests/testdata/npm/peer_deps_with_copied_folders/main_info_json.out b/cli/tests/testdata/npm/peer_deps_with_copied_folders/main_info_json.out new file mode 100644 index 000000000..634ec6251 --- /dev/null +++ b/cli/tests/testdata/npm/peer_deps_with_copied_folders/main_info_json.out @@ -0,0 +1,95 @@ +{ + "roots": [ + "[WILDCARD]/npm/peer_deps_with_copied_folders/main.ts" + ], + "modules": [ + { + "dependencies": [ + { + "specifier": "npm:@denotest/peer-dep-test-child@1", + "code": { + "specifier": "npm:@denotest/peer-dep-test-child@1", + "span": { + "start": { + "line": 0, + "character": 21 + }, + "end": { + "line": 0, + "character": 58 + } + } + }, + "npmPackage": "@denotest/peer-dep-test-child@1.0.0_@denotest+peer-dep-test-peer@1.0.0" + }, + { + "specifier": "npm:@denotest/peer-dep-test-child@2", + "code": { + "specifier": "npm:@denotest/peer-dep-test-child@2", + "span": { + "start": { + "line": 1, + "character": 21 + }, + "end": { + "line": 1, + "character": 58 + } + } + }, + "npmPackage": "@denotest/peer-dep-test-child@2.0.0_@denotest+peer-dep-test-peer@2.0.0" + } + ], + "kind": "esm", + "local": "[WILDCARD]main.ts", + "emit": null, + "map": null, + "size": 171, + "mediaType": "TypeScript", + "specifier": "file://[WILDCARD]/main.ts" + } + ], + "redirects": {}, + "npmPackages": { + "@denotest/peer-dep-test-child@1.0.0_@denotest+peer-dep-test-peer@1.0.0": { + "name": "@denotest/peer-dep-test-child", + "version": "1.0.0", + "dependencies": [ + "@denotest/peer-dep-test-grandchild@1.0.0_@denotest+peer-dep-test-peer@1.0.0", + "@denotest/peer-dep-test-peer@1.0.0" + ] + }, + "@denotest/peer-dep-test-child@2.0.0_@denotest+peer-dep-test-peer@2.0.0": { + "name": "@denotest/peer-dep-test-child", + "version": "2.0.0", + "dependencies": [ + "@denotest/peer-dep-test-grandchild@1.0.0_@denotest+peer-dep-test-peer@2.0.0", + "@denotest/peer-dep-test-peer@2.0.0" + ] + }, + "@denotest/peer-dep-test-grandchild@1.0.0_@denotest+peer-dep-test-peer@1.0.0": { + "name": "@denotest/peer-dep-test-grandchild", + "version": "1.0.0", + "dependencies": [ + "@denotest/peer-dep-test-peer@1.0.0" + ] + }, + "@denotest/peer-dep-test-grandchild@1.0.0_@denotest+peer-dep-test-peer@2.0.0": { + "name": "@denotest/peer-dep-test-grandchild", + "version": "1.0.0", + "dependencies": [ + "@denotest/peer-dep-test-peer@2.0.0" + ] + }, + "@denotest/peer-dep-test-peer@1.0.0": { + "name": "@denotest/peer-dep-test-peer", + "version": "1.0.0", + "dependencies": [] + }, + "@denotest/peer-dep-test-peer@2.0.0": { + "name": "@denotest/peer-dep-test-peer", + "version": "2.0.0", + "dependencies": [] + } + } +} diff --git a/cli/tests/testdata/npm/registry/@denotest/peer-dep-test-child/1.0.0/index.js b/cli/tests/testdata/npm/registry/@denotest/peer-dep-test-child/1.0.0/index.js new file mode 100644 index 000000000..636ec3c35 --- /dev/null +++ b/cli/tests/testdata/npm/registry/@denotest/peer-dep-test-child/1.0.0/index.js @@ -0,0 +1 @@ +module.exports = require("@denotest/peer-dep-test-grandchild"); diff --git a/cli/tests/testdata/npm/registry/@denotest/peer-dep-test-child/1.0.0/package.json b/cli/tests/testdata/npm/registry/@denotest/peer-dep-test-child/1.0.0/package.json new file mode 100644 index 000000000..32eb49851 --- /dev/null +++ b/cli/tests/testdata/npm/registry/@denotest/peer-dep-test-child/1.0.0/package.json @@ -0,0 +1,8 @@ +{ + "name": "@denotest/peer-dep-test-child", + "version": "1.0.0", + "dependencies": { + "@denotest/peer-dep-test-grandchild": "*", + "@denotest/peer-dep-test-peer": "^1" + } +} diff --git a/cli/tests/testdata/npm/registry/@denotest/peer-dep-test-child/2.0.0/index.js b/cli/tests/testdata/npm/registry/@denotest/peer-dep-test-child/2.0.0/index.js new file mode 100644 index 000000000..636ec3c35 --- /dev/null +++ b/cli/tests/testdata/npm/registry/@denotest/peer-dep-test-child/2.0.0/index.js @@ -0,0 +1 @@ +module.exports = require("@denotest/peer-dep-test-grandchild"); diff --git a/cli/tests/testdata/npm/registry/@denotest/peer-dep-test-child/2.0.0/package.json b/cli/tests/testdata/npm/registry/@denotest/peer-dep-test-child/2.0.0/package.json new file mode 100644 index 000000000..3c82c01f9 --- /dev/null +++ b/cli/tests/testdata/npm/registry/@denotest/peer-dep-test-child/2.0.0/package.json @@ -0,0 +1,8 @@ +{ + "name": "@denotest/peer-dep-test-child", + "version": "2.0.0", + "dependencies": { + "@denotest/peer-dep-test-grandchild": "*", + "@denotest/peer-dep-test-peer": "^2" + } +} diff --git a/cli/tests/testdata/npm/registry/@denotest/peer-dep-test-grandchild/1.0.0/dist/index.js b/cli/tests/testdata/npm/registry/@denotest/peer-dep-test-grandchild/1.0.0/dist/index.js new file mode 100644 index 000000000..9a0d9730b --- /dev/null +++ b/cli/tests/testdata/npm/registry/@denotest/peer-dep-test-grandchild/1.0.0/dist/index.js @@ -0,0 +1 @@ +module.exports = require("@denotest/peer-dep-test-peer"); diff --git a/cli/tests/testdata/npm/registry/@denotest/peer-dep-test-grandchild/1.0.0/index.js b/cli/tests/testdata/npm/registry/@denotest/peer-dep-test-grandchild/1.0.0/index.js new file mode 100644 index 000000000..7d44863df --- /dev/null +++ b/cli/tests/testdata/npm/registry/@denotest/peer-dep-test-grandchild/1.0.0/index.js @@ -0,0 +1 @@ +module.exports = require("./dist/index"); diff --git a/cli/tests/testdata/npm/registry/@denotest/peer-dep-test-grandchild/1.0.0/package.json b/cli/tests/testdata/npm/registry/@denotest/peer-dep-test-grandchild/1.0.0/package.json new file mode 100644 index 000000000..845ef414d --- /dev/null +++ b/cli/tests/testdata/npm/registry/@denotest/peer-dep-test-grandchild/1.0.0/package.json @@ -0,0 +1,7 @@ +{ + "name": "@denotest/peer-dep-test-child-2", + "version": "1.0.0", + "peerDependencies": { + "@denotest/peer-dep-test-peer": "*" + } +} diff --git a/cli/tests/testdata/npm/registry/@denotest/peer-dep-test-peer/1.0.0/index.js b/cli/tests/testdata/npm/registry/@denotest/peer-dep-test-peer/1.0.0/index.js new file mode 100644 index 000000000..bd816eaba --- /dev/null +++ b/cli/tests/testdata/npm/registry/@denotest/peer-dep-test-peer/1.0.0/index.js @@ -0,0 +1 @@ +module.exports = 1; diff --git a/cli/tests/testdata/npm/registry/@denotest/peer-dep-test-peer/1.0.0/package.json b/cli/tests/testdata/npm/registry/@denotest/peer-dep-test-peer/1.0.0/package.json new file mode 100644 index 000000000..cedb3609e --- /dev/null +++ b/cli/tests/testdata/npm/registry/@denotest/peer-dep-test-peer/1.0.0/package.json @@ -0,0 +1,4 @@ +{ + "name": "@denotest/peer-dep-test-peer", + "version": "1.0.0" +} diff --git a/cli/tests/testdata/npm/registry/@denotest/peer-dep-test-peer/2.0.0/index.js b/cli/tests/testdata/npm/registry/@denotest/peer-dep-test-peer/2.0.0/index.js new file mode 100644 index 000000000..4bbffde10 --- /dev/null +++ b/cli/tests/testdata/npm/registry/@denotest/peer-dep-test-peer/2.0.0/index.js @@ -0,0 +1 @@ +module.exports = 2; diff --git a/cli/tests/testdata/npm/registry/@denotest/peer-dep-test-peer/2.0.0/package.json b/cli/tests/testdata/npm/registry/@denotest/peer-dep-test-peer/2.0.0/package.json new file mode 100644 index 000000000..90c24f875 --- /dev/null +++ b/cli/tests/testdata/npm/registry/@denotest/peer-dep-test-peer/2.0.0/package.json @@ -0,0 +1,4 @@ +{ + "name": "@denotest/peer-dep-test-peer", + "version": "2.0.0" +} -- cgit v1.2.3