From e958340bf8d584e84d54b593198c6e137e783fe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Mon, 22 Apr 2024 21:38:17 +0200 Subject: [PATCH 01/18] wip --- Cargo.lock | 4 ---- Cargo.toml | 4 ++++ cli/npm/managed/resolvers/local.rs | 24 ++++++++++++++++++++++++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a4e90408927e8..4c9ea238bd51c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1741,8 +1741,6 @@ dependencies = [ [[package]] name = "deno_npm" version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53a333104d3fb6aa52e499384e523aefc09d3ac8ecd05ca7f65f856044fbcb09" dependencies = [ "anyhow", "async-trait", @@ -2532,8 +2530,6 @@ dependencies = [ [[package]] name = "eszip" version = "0.68.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de7a1c16d3d63e7c84e14da935cebb4b7f451d4508b3ff2ae0e1c313059cd1" dependencies = [ "anyhow", "base64", diff --git a/Cargo.toml b/Cargo.toml index ef8353a2fe98e..841d4db920e62 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -370,3 +370,7 @@ opt-level = 3 opt-level = 3 [profile.release.package.base64-simd] opt-level = 3 + +[patch.crates-io] +deno_npm = { path = "../deno_npm" } +eszip = { path = "../eszip" } diff --git a/cli/npm/managed/resolvers/local.rs b/cli/npm/managed/resolvers/local.rs index 34e0fae098a1b..99571444f30a3 100644 --- a/cli/npm/managed/resolvers/local.rs +++ b/cli/npm/managed/resolvers/local.rs @@ -265,6 +265,10 @@ async fn sync_resolution_with_fs( fs::create_dir_all(&deno_node_modules_dir).with_context(|| { format!("Creating '{}'", deno_local_registry_dir.display()) })?; + let bin_node_modules_dir_path = root_node_modules_dir_path.join(".bin"); + fs::create_dir_all(&bin_node_modules_dir_path).with_context(|| { + format!("Creating '{}'", bin_node_modules_dir_path.display()) + })?; let single_process_lock = LaxSingleProcessFsFlag::lock( deno_local_registry_dir.join(".deno.lock"), @@ -340,6 +344,26 @@ async fn sync_resolution_with_fs( } // write out a file that indicates this folder has been initialized fs::write(initialized_file, "")?; + // set up an entry in `node_modules/.bin/` if package provides bin entry(ies) + if let Some(bin_entries) = &package.bin { + match bin_entries { + deno_npm::registry::NpmPackageVersionBinEntry::String(script) => { + let name = package.id.nv.name; + eprintln!( + "need to setup \"{}\" ({}) in `node_modules/.bin/", + name, script + ); + } + deno_npm::registry::NpmPackageVersionBinEntry::Map(entries) => { + for (name, script) in entries { + eprintln!( + "need to setup \"{}\" ({}) in `node_modules/.bin/", + name, script + ); + } + } + } + } // finally stop showing the progress bar drop(pb_guard); // explicit for clarity Ok(()) From 633a07892d9a9b4092a128dd07a5fae4f0cff8a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Mon, 22 Apr 2024 23:10:55 +0200 Subject: [PATCH 02/18] it works on unix --- cli/npm/managed/resolvers/local.rs | 89 +++++++++++++++++++++++------- 1 file changed, 70 insertions(+), 19 deletions(-) diff --git a/cli/npm/managed/resolvers/local.rs b/cli/npm/managed/resolvers/local.rs index 99571444f30a3..e4356a7233a81 100644 --- a/cli/npm/managed/resolvers/local.rs +++ b/cli/npm/managed/resolvers/local.rs @@ -7,6 +7,7 @@ use std::cmp::Ordering; use std::collections::HashMap; use std::collections::HashSet; use std::fs; +use std::os::unix::fs::symlink; use std::path::Path; use std::path::PathBuf; use std::sync::Arc; @@ -24,6 +25,7 @@ use deno_ast::ModuleSpecifier; use deno_core::anyhow::bail; use deno_core::anyhow::Context; use deno_core::error::AnyError; +use deno_core::parking_lot::Mutex; use deno_core::unsync::spawn; use deno_core::unsync::JoinHandle; use deno_core::url::Url; @@ -293,6 +295,7 @@ async fn sync_resolution_with_fs( Vec::with_capacity(package_partitions.packages.len()); let mut newest_packages_by_name: HashMap<&String, &NpmResolutionPackage> = HashMap::with_capacity(package_partitions.packages.len()); + let bin_entries_to_setup = Arc::new(Mutex::new(Vec::with_capacity(16))); for package in &package_partitions.packages { if let Some(current_pkg) = newest_packages_by_name.get_mut(&package.id.nv.name) @@ -321,6 +324,7 @@ async fn sync_resolution_with_fs( let cache = cache.clone(); let registry_url = registry_url.clone(); let package = package.clone(); + let bin_entries_to_setup = bin_entries_to_setup.clone(); let handle = spawn(async move { cache .ensure_package(&package.id.nv, &package.dist, ®istry_url) @@ -344,26 +348,11 @@ async fn sync_resolution_with_fs( } // write out a file that indicates this folder has been initialized fs::write(initialized_file, "")?; - // set up an entry in `node_modules/.bin/` if package provides bin entry(ies) - if let Some(bin_entries) = &package.bin { - match bin_entries { - deno_npm::registry::NpmPackageVersionBinEntry::String(script) => { - let name = package.id.nv.name; - eprintln!( - "need to setup \"{}\" ({}) in `node_modules/.bin/", - name, script - ); - } - deno_npm::registry::NpmPackageVersionBinEntry::Map(entries) => { - for (name, script) in entries { - eprintln!( - "need to setup \"{}\" ({}) in `node_modules/.bin/", - name, script - ); - } - } - } + + if package.bin.is_some() { + bin_entries_to_setup.lock().push((package.clone(), package_path)); } + // finally stop showing the progress bar drop(pb_guard); // explicit for clarity Ok(()) @@ -494,6 +483,35 @@ async fn sync_resolution_with_fs( } } + + // 6. Set up `node_modules/.bin` entries for packages that need it. + for (package, package_path) in &*bin_entries_to_setup.lock() { + let package = snapshot.package_from_id(&package.id).unwrap(); + if let Some(bin_entries) = &package.bin { + match bin_entries { + deno_npm::registry::NpmPackageVersionBinEntry::String(script) => { + let name = &package.id.nv.name; + symlink_bin_entry( + name, + script, + &package_path, + &bin_node_modules_dir_path, + )?; + } + deno_npm::registry::NpmPackageVersionBinEntry::Map(entries) => { + for (name, script) in entries { + symlink_bin_entry( + name, + script, + &package_path, + &bin_node_modules_dir_path, + )?; + } + } + } + } + } + setup_cache.save(); drop(single_process_lock); drop(pb_clear_guard); @@ -670,6 +688,39 @@ fn get_package_folder_id_from_folder_name( }) } +fn symlink_bin_entry( + bin_name: &str, + bin_script: &str, + package_path: &Path, + bin_node_modules_dir_path: &Path, +) -> Result<(), AnyError> { + let link = bin_node_modules_dir_path.join(bin_name); + let original = package_path.join(bin_script); + + // Don't bother setting up another link if it already exists + if link.exists() { + let resolved = std::fs::read_link(&link).ok(); + if let Some(resolved) = resolved { + if resolved != original { + log::warn!( + "{} Trying to set up '{}' bin for \"{}\", but an entry pointing to \"{}\" already exists. Skipping...", + deno_terminal::colors::yellow("Warning"), + bin_name, + resolved.display(), + original.display() + ); + return Ok(()); + } + } + } + + // TODO: handle Windows + symlink(&original, &link).with_context(|| { + format!("Can't set up '{}' bin at {}", bin_name, link.display()) + })?; + Ok(()) +} + fn symlink_package_dir( old_path: &Path, new_path: &Path, From be289c1123ac90af13b1772add98cbbdb6f4a300 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Tue, 23 Apr 2024 20:47:41 +0200 Subject: [PATCH 03/18] bump eszip and deno_npm --- Cargo.lock | 8 ++++++-- Cargo.toml | 4 ---- cli/Cargo.toml | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7fc412885a738..7c9f05dd8607c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1740,7 +1740,9 @@ dependencies = [ [[package]] name = "deno_npm" -version = "0.17.0" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcd4f91bb7139c031791f135aa1785e08a828795d5daaefe981b9f9292f66e91" dependencies = [ "anyhow", "async-trait", @@ -2529,7 +2531,9 @@ dependencies = [ [[package]] name = "eszip" -version = "0.68.0" +version = "0.68.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "697c530ec5a23c26227ae32f1772b3346236c7f72fea630c54e1977f0150e5f4" dependencies = [ "anyhow", "base64", diff --git a/Cargo.toml b/Cargo.toml index e694390d7cd68..f0fafb688ad37 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -370,7 +370,3 @@ opt-level = 3 opt-level = 3 [profile.release.package.base64-simd] opt-level = 3 - -[patch.crates-io] -deno_npm = { path = "../deno_npm" } -eszip = { path = "../eszip" } diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 4f25e684370bc..6219938f085df 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -71,12 +71,12 @@ deno_emit = "=0.40.0" deno_graph = { version = "=0.73.1", features = ["tokio_executor"] } deno_lint = { version = "=0.58.3", features = ["docs"] } deno_lockfile.workspace = true -deno_npm = "=0.17.0" +deno_npm = "=0.18.0" deno_runtime = { workspace = true, features = ["include_js_files_for_snapshotting"] } deno_semver = "=0.5.4" deno_task_shell = "=0.16.0" deno_terminal.workspace = true -eszip = "=0.68.0" +eszip = "=0.68.1" napi_sym.workspace = true async-trait.workspace = true From 727ff9f6f95cee1d660ad407212709a3758daeeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 10 May 2024 00:46:41 +0200 Subject: [PATCH 04/18] fmt --- cli/npm/managed/resolvers/local.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cli/npm/managed/resolvers/local.rs b/cli/npm/managed/resolvers/local.rs index bfb2423c59dd6..d33f60c38c162 100644 --- a/cli/npm/managed/resolvers/local.rs +++ b/cli/npm/managed/resolvers/local.rs @@ -352,7 +352,9 @@ async fn sync_resolution_with_fs( fs::write(initialized_file, "")?; if package.bin.is_some() { - bin_entries_to_setup.lock().push((package.clone(), package_path)); + bin_entries_to_setup + .lock() + .push((package.clone(), package_path)); } // finally stop showing the progress bar @@ -485,7 +487,6 @@ async fn sync_resolution_with_fs( } } - // 6. Set up `node_modules/.bin` entries for packages that need it. for (package, package_path) in &*bin_entries_to_setup.lock() { let package = snapshot.package_from_id(&package.id).unwrap(); @@ -707,8 +708,8 @@ fn symlink_bin_entry( log::warn!( "{} Trying to set up '{}' bin for \"{}\", but an entry pointing to \"{}\" already exists. Skipping...", deno_terminal::colors::yellow("Warning"), - bin_name, - resolved.display(), + bin_name, + resolved.display(), original.display() ); return Ok(()); From 6e28847413741d86a2f839aebf7b5f6a93fd9553 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 10 May 2024 01:43:37 +0200 Subject: [PATCH 05/18] reset CI From 7dc69cda4a5ecbfce8213ebbcff2473dd6d689e2 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Tue, 14 May 2024 16:00:30 -0700 Subject: [PATCH 06/18] Create dir, set executable, fix link for inferred bin name --- cli/npm/managed/resolvers/local.rs | 66 +++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 19 deletions(-) diff --git a/cli/npm/managed/resolvers/local.rs b/cli/npm/managed/resolvers/local.rs index d33f60c38c162..a77e058155461 100644 --- a/cli/npm/managed/resolvers/local.rs +++ b/cli/npm/managed/resolvers/local.rs @@ -488,21 +488,24 @@ async fn sync_resolution_with_fs( } // 6. Set up `node_modules/.bin` entries for packages that need it. - for (package, package_path) in &*bin_entries_to_setup.lock() { - let package = snapshot.package_from_id(&package.id).unwrap(); - if let Some(bin_entries) = &package.bin { - match bin_entries { - deno_npm::registry::NpmPackageVersionBinEntry::String(script) => { - let name = &package.id.nv.name; - symlink_bin_entry( - name, - script, - &package_path, - &bin_node_modules_dir_path, - )?; - } - deno_npm::registry::NpmPackageVersionBinEntry::Map(entries) => { - for (name, script) in entries { + { + let bin_entries = bin_entries_to_setup.lock(); + if !bin_entries.is_empty() && !bin_node_modules_dir_path.exists() { + fs::create_dir_all(&bin_node_modules_dir_path).with_context(|| { + format!("Creating '{}'", bin_node_modules_dir_path.display()) + })?; + } + for (package, package_path) in &*bin_entries { + let package = snapshot.package_from_id(&package.id).unwrap(); + if let Some(bin_entries) = &package.bin { + match bin_entries { + deno_npm::registry::NpmPackageVersionBinEntry::String(script) => { + let name = package + .id + .nv + .name + .rsplit_once('/') + .map_or(package.id.nv.name.as_str(), |(_, name)| name); symlink_bin_entry( name, script, @@ -510,6 +513,16 @@ async fn sync_resolution_with_fs( &bin_node_modules_dir_path, )?; } + deno_npm::registry::NpmPackageVersionBinEntry::Map(entries) => { + for (name, script) in entries { + symlink_bin_entry( + name, + script, + &package_path, + &bin_node_modules_dir_path, + )?; + } + } } } } @@ -717,10 +730,25 @@ fn symlink_bin_entry( } } - // TODO: handle Windows - symlink(&original, &link).with_context(|| { - format!("Can't set up '{}' bin at {}", bin_name, link.display()) - })?; + #[cfg(target_family = "windows")] + { + todo!(); + } + #[cfg(target_family = "unix")] + { + use std::os::unix::fs::PermissionsExt; + let mut perms = std::fs::metadata(&original).unwrap().permissions(); + if perms.mode() & 0o111 == 0 { + // if the original file is not executable, make it executable + perms.set_mode(perms.mode() | 0o111); + std::fs::set_permissions(&original, perms).with_context(|| { + format!("Setting permissions on '{}'", original.display()) + })?; + } + symlink(&original, &link).with_context(|| { + format!("Can't set up '{}' bin at {}", bin_name, link.display()) + })?; + } Ok(()) } From ad0f5785329a42e279aa24178bd9b7849e19c8e0 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Tue, 14 May 2024 16:04:10 -0700 Subject: [PATCH 07/18] Add shebang --- tests/registry/npm/@denotest/bin/1.0.0/cli.mjs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/registry/npm/@denotest/bin/1.0.0/cli.mjs b/tests/registry/npm/@denotest/bin/1.0.0/cli.mjs index 0ae8e91903304..7f6d1793a0079 100644 --- a/tests/registry/npm/@denotest/bin/1.0.0/cli.mjs +++ b/tests/registry/npm/@denotest/bin/1.0.0/cli.mjs @@ -1,3 +1,5 @@ +#!/usr/bin/env -S node + import process from "node:process"; for (const arg of process.argv.slice(2)) { From 2c58b39ae00931842cb82f1829712aa5c0a65e73 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Tue, 14 May 2024 16:09:11 -0700 Subject: [PATCH 08/18] Add basic test --- tests/specs/task/bin_package/__test__.jsonc | 25 +++++++++++++++++++++ tests/specs/task/bin_package/deno.json | 3 +++ tests/specs/task/bin_package/npm-run.out | 6 +++++ tests/specs/task/bin_package/package.json | 9 ++++++++ tests/specs/task/bin_package/task.out | 6 +++++ 5 files changed, 49 insertions(+) create mode 100644 tests/specs/task/bin_package/__test__.jsonc create mode 100644 tests/specs/task/bin_package/deno.json create mode 100644 tests/specs/task/bin_package/npm-run.out create mode 100644 tests/specs/task/bin_package/package.json create mode 100644 tests/specs/task/bin_package/task.out diff --git a/tests/specs/task/bin_package/__test__.jsonc b/tests/specs/task/bin_package/__test__.jsonc new file mode 100644 index 0000000000000..9a055726bfe5f --- /dev/null +++ b/tests/specs/task/bin_package/__test__.jsonc @@ -0,0 +1,25 @@ +{ + "tests": [ + + ] + "tempDir": true, + "steps": [ + // {"commandName": "npm", "args": "install", "output": "\nadded 1 package in [WILDCARD]\n"}, + { + "args": "task sayhi", + "output": "task.out" + }, + { + "if": "unix", + "commandName": "./node_modules/.bin/cli-esm", + "args": "hi hello", + "output": "hi\nhello\n" + }, + + { + "commandName": "npm", + "args": "run sayhi", + "output": "npm-run.out" + } + ] +} diff --git a/tests/specs/task/bin_package/deno.json b/tests/specs/task/bin_package/deno.json new file mode 100644 index 0000000000000..176354f98fada --- /dev/null +++ b/tests/specs/task/bin_package/deno.json @@ -0,0 +1,3 @@ +{ + "nodeModulesDir": true +} diff --git a/tests/specs/task/bin_package/npm-run.out b/tests/specs/task/bin_package/npm-run.out new file mode 100644 index 0000000000000..0c636787a590d --- /dev/null +++ b/tests/specs/task/bin_package/npm-run.out @@ -0,0 +1,6 @@ + +> sayhi +> cli-esm hi hello + +hi +hello diff --git a/tests/specs/task/bin_package/package.json b/tests/specs/task/bin_package/package.json new file mode 100644 index 0000000000000..c0a34548f5700 --- /dev/null +++ b/tests/specs/task/bin_package/package.json @@ -0,0 +1,9 @@ +{ + "name": "bin_package", + "devDependencies": { + "@denotest/bin": "1.0.0" + }, + "scripts": { + "sayhi": "cli-esm hi hello" + } +} diff --git a/tests/specs/task/bin_package/task.out b/tests/specs/task/bin_package/task.out new file mode 100644 index 0000000000000..69b4f75082256 --- /dev/null +++ b/tests/specs/task/bin_package/task.out @@ -0,0 +1,6 @@ +Download http://localhost:4260/@denotest/bin +Download http://localhost:4260/@denotest/bin/1.0.0.tgz +Initialize @denotest/bin@1.0.0 +Task sayhi cli-esm hi hello +hi +hello From 8fa9b1eb2d57e0857eee8fa0197c3246260fd3ce Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Tue, 14 May 2024 17:47:11 -0700 Subject: [PATCH 09/18] Use relative symlinks --- Cargo.lock | 1 + cli/Cargo.toml | 1 + cli/npm/managed/resolvers/local.rs | 11 +++++++++-- tests/specs/task/bin_package/__test__.jsonc | 3 --- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 40d9057916146..fd6f41e0b2b37 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1133,6 +1133,7 @@ dependencies = [ "open", "os_pipe", "p256", + "pathdiff", "percent-encoding", "phf 0.11.2", "pin-project", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 6d88c5614f2bd..ea7f9de58a186 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -123,6 +123,7 @@ once_cell.workspace = true open = "5.0.1" os_pipe.workspace = true p256.workspace = true +pathdiff = "0.2.1" percent-encoding.workspace = true phf.workspace = true pin-project.workspace = true diff --git a/cli/npm/managed/resolvers/local.rs b/cli/npm/managed/resolvers/local.rs index a77e058155461..6cc0eb6d9918d 100644 --- a/cli/npm/managed/resolvers/local.rs +++ b/cli/npm/managed/resolvers/local.rs @@ -745,8 +745,15 @@ fn symlink_bin_entry( format!("Setting permissions on '{}'", original.display()) })?; } - symlink(&original, &link).with_context(|| { - format!("Can't set up '{}' bin at {}", bin_name, link.display()) + let original_relative = + pathdiff::diff_paths(&original, bin_node_modules_dir_path) + .unwrap_or(original); + symlink(&original_relative, &link).with_context(|| { + format!( + "Can't set up '{}' bin at {}", + bin_name, + original_relative.display() + ) })?; } Ok(()) diff --git a/tests/specs/task/bin_package/__test__.jsonc b/tests/specs/task/bin_package/__test__.jsonc index 9a055726bfe5f..bd4469d900152 100644 --- a/tests/specs/task/bin_package/__test__.jsonc +++ b/tests/specs/task/bin_package/__test__.jsonc @@ -1,7 +1,4 @@ { - "tests": [ - - ] "tempDir": true, "steps": [ // {"commandName": "npm", "args": "install", "output": "\nadded 1 package in [WILDCARD]\n"}, From 067083809dc42f1c199993ad2c796219b17a0c35 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Tue, 14 May 2024 18:44:00 -0700 Subject: [PATCH 10/18] Appease clippy --- cli/npm/managed/resolvers/local.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/npm/managed/resolvers/local.rs b/cli/npm/managed/resolvers/local.rs index 6cc0eb6d9918d..5f2a46cd37401 100644 --- a/cli/npm/managed/resolvers/local.rs +++ b/cli/npm/managed/resolvers/local.rs @@ -509,7 +509,7 @@ async fn sync_resolution_with_fs( symlink_bin_entry( name, script, - &package_path, + package_path, &bin_node_modules_dir_path, )?; } @@ -518,7 +518,7 @@ async fn sync_resolution_with_fs( symlink_bin_entry( name, script, - &package_path, + package_path, &bin_node_modules_dir_path, )?; } From 4958e17746b811a1d1164762506ca0a6a24e23dc Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Wed, 15 May 2024 18:12:53 -0700 Subject: [PATCH 11/18] Add windows support --- cli/npm/managed/resolvers/local.rs | 65 ++--------- .../managed/resolvers/local/bin_entries.rs | 107 ++++++++++++++++++ tests/specs/task/bin_package/__test__.jsonc | 7 +- 3 files changed, 120 insertions(+), 59 deletions(-) create mode 100644 cli/npm/managed/resolvers/local/bin_entries.rs diff --git a/cli/npm/managed/resolvers/local.rs b/cli/npm/managed/resolvers/local.rs index 5f2a46cd37401..37b6a1a19af80 100644 --- a/cli/npm/managed/resolvers/local.rs +++ b/cli/npm/managed/resolvers/local.rs @@ -2,12 +2,13 @@ //! Code for local node_modules resolution. +mod bin_entries; + use std::borrow::Cow; use std::cmp::Ordering; use std::collections::HashMap; use std::collections::HashSet; use std::fs; -use std::os::unix::fs::symlink; use std::path::Path; use std::path::PathBuf; use std::sync::Arc; @@ -500,13 +501,15 @@ async fn sync_resolution_with_fs( if let Some(bin_entries) = &package.bin { match bin_entries { deno_npm::registry::NpmPackageVersionBinEntry::String(script) => { + // the default bin name doesn't include the organization let name = package .id .nv .name .rsplit_once('/') .map_or(package.id.nv.name.as_str(), |(_, name)| name); - symlink_bin_entry( + bin_entries::set_up_bin_entry( + package, name, script, package_path, @@ -515,7 +518,8 @@ async fn sync_resolution_with_fs( } deno_npm::registry::NpmPackageVersionBinEntry::Map(entries) => { for (name, script) in entries { - symlink_bin_entry( + bin_entries::set_up_bin_entry( + package, name, script, package_path, @@ -704,61 +708,6 @@ fn get_package_folder_id_from_folder_name( }) } -fn symlink_bin_entry( - bin_name: &str, - bin_script: &str, - package_path: &Path, - bin_node_modules_dir_path: &Path, -) -> Result<(), AnyError> { - let link = bin_node_modules_dir_path.join(bin_name); - let original = package_path.join(bin_script); - - // Don't bother setting up another link if it already exists - if link.exists() { - let resolved = std::fs::read_link(&link).ok(); - if let Some(resolved) = resolved { - if resolved != original { - log::warn!( - "{} Trying to set up '{}' bin for \"{}\", but an entry pointing to \"{}\" already exists. Skipping...", - deno_terminal::colors::yellow("Warning"), - bin_name, - resolved.display(), - original.display() - ); - return Ok(()); - } - } - } - - #[cfg(target_family = "windows")] - { - todo!(); - } - #[cfg(target_family = "unix")] - { - use std::os::unix::fs::PermissionsExt; - let mut perms = std::fs::metadata(&original).unwrap().permissions(); - if perms.mode() & 0o111 == 0 { - // if the original file is not executable, make it executable - perms.set_mode(perms.mode() | 0o111); - std::fs::set_permissions(&original, perms).with_context(|| { - format!("Setting permissions on '{}'", original.display()) - })?; - } - let original_relative = - pathdiff::diff_paths(&original, bin_node_modules_dir_path) - .unwrap_or(original); - symlink(&original_relative, &link).with_context(|| { - format!( - "Can't set up '{}' bin at {}", - bin_name, - original_relative.display() - ) - })?; - } - Ok(()) -} - fn symlink_package_dir( old_path: &Path, new_path: &Path, diff --git a/cli/npm/managed/resolvers/local/bin_entries.rs b/cli/npm/managed/resolvers/local/bin_entries.rs new file mode 100644 index 0000000000000..7f65cf06f31be --- /dev/null +++ b/cli/npm/managed/resolvers/local/bin_entries.rs @@ -0,0 +1,107 @@ +use crate::npm::managed::NpmResolutionPackage; +use deno_core::anyhow::Context; +use deno_core::error::AnyError; +use std::fs; +use std::path::Path; + +pub(super) fn set_up_bin_entry( + package: &NpmResolutionPackage, + bin_name: &str, + #[allow(unused_variables)] bin_script: &str, + #[allow(unused_variables)] package_path: &Path, + bin_node_modules_dir_path: &Path, +) -> Result<(), AnyError> { + #[cfg(windows)] + { + set_up_bin_shim(package, bin_name, bin_node_modules_dir_path)?; + } + #[cfg(unix)] + { + symlink_bin_entry( + package, + bin_name, + bin_script, + package_path, + bin_node_modules_dir_path, + )?; + } + Ok(()) +} + +#[cfg(windows)] +fn set_up_bin_shim( + package: &NpmResolutionPackage, + bin_name: &str, + bin_node_modules_dir_path: &Path, +) -> Result<(), AnyError> { + let mut cmd_shim = bin_node_modules_dir_path.join(bin_name); + + cmd_shim.set_extension("cmd"); + let shim = format!("@deno run -A npm:{}/{bin_name} %*", package.id.nv); + if cmd_shim.exists() { + if let Ok(contents) = fs::read_to_string(cmd_shim) { + if contents == shim { + // up to date + return Ok(()); + } + } + return Ok(()); + } + fs::write(&cmd_shim, shim).with_context(|| { + format!("Can't set up '{}' bin at {}", bin_name, cmd_shim.display()) + })?; + + Ok(()) +} + +#[cfg(unix)] +fn symlink_bin_entry( + package: &NpmResolutionPackage, + bin_name: &str, + bin_script: &str, + package_path: &Path, + bin_node_modules_dir_path: &Path, +) -> Result<(), AnyError> { + use std::os::unix::fs::symlink; + let link = bin_node_modules_dir_path.join(bin_name); + let original = package_path.join(bin_script); + + // Don't bother setting up another link if it already exists + if link.exists() { + let resolved = std::fs::read_link(&link).ok(); + if let Some(resolved) = resolved { + if resolved != original { + log::warn!( + "{} Trying to set up '{}' bin for \"{}\", but an entry pointing to \"{}\" already exists. Skipping...", + deno_terminal::colors::yellow("Warning"), + bin_name, + resolved.display(), + original.display() + ); + } + return Ok(()); + } + } + + use std::os::unix::fs::PermissionsExt; + let mut perms = std::fs::metadata(&original).unwrap().permissions(); + if perms.mode() & 0o111 == 0 { + // if the original file is not executable, make it executable + perms.set_mode(perms.mode() | 0o111); + std::fs::set_permissions(&original, perms).with_context(|| { + format!("Setting permissions on '{}'", original.display()) + })?; + } + let original_relative = + pathdiff::diff_paths(&original, bin_node_modules_dir_path) + .unwrap_or(original); + symlink(&original_relative, &link).with_context(|| { + format!( + "Can't set up '{}' bin at {}", + bin_name, + original_relative.display() + ) + })?; + + Ok(()) +} diff --git a/tests/specs/task/bin_package/__test__.jsonc b/tests/specs/task/bin_package/__test__.jsonc index bd4469d900152..bc8321e5dc2ea 100644 --- a/tests/specs/task/bin_package/__test__.jsonc +++ b/tests/specs/task/bin_package/__test__.jsonc @@ -12,7 +12,12 @@ "args": "hi hello", "output": "hi\nhello\n" }, - + { + "if": "windows", + "commandName": "./node_modules/.bin/cli-esm.cmd", + "args": "hi hello", + "output": "hi\nhello\n" + }, { "commandName": "npm", "args": "run sayhi", From d5d5530ac8a99281c804b9aedfe34f2746443fcd Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Tue, 21 May 2024 09:46:21 -0700 Subject: [PATCH 12/18] Appease linter --- cli/npm/managed/resolvers/local/bin_entries.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cli/npm/managed/resolvers/local/bin_entries.rs b/cli/npm/managed/resolvers/local/bin_entries.rs index 7f65cf06f31be..e8e470396adea 100644 --- a/cli/npm/managed/resolvers/local/bin_entries.rs +++ b/cli/npm/managed/resolvers/local/bin_entries.rs @@ -1,7 +1,8 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + use crate::npm::managed::NpmResolutionPackage; use deno_core::anyhow::Context; use deno_core::error::AnyError; -use std::fs; use std::path::Path; pub(super) fn set_up_bin_entry( @@ -34,6 +35,7 @@ fn set_up_bin_shim( bin_name: &str, bin_node_modules_dir_path: &Path, ) -> Result<(), AnyError> { + use std::fs; let mut cmd_shim = bin_node_modules_dir_path.join(bin_name); cmd_shim.set_extension("cmd"); @@ -56,7 +58,7 @@ fn set_up_bin_shim( #[cfg(unix)] fn symlink_bin_entry( - package: &NpmResolutionPackage, + _package: &NpmResolutionPackage, bin_name: &str, bin_script: &str, package_path: &Path, From f31dc9be3d25035a62120ed08a9b31d67a8c506d Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Tue, 21 May 2024 16:19:47 -0700 Subject: [PATCH 13/18] Factor out relative_path util --- cli/npm/managed/resolvers/local/bin_entries.rs | 2 +- cli/util/path.rs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/cli/npm/managed/resolvers/local/bin_entries.rs b/cli/npm/managed/resolvers/local/bin_entries.rs index e8e470396adea..8e43cf98b6ac7 100644 --- a/cli/npm/managed/resolvers/local/bin_entries.rs +++ b/cli/npm/managed/resolvers/local/bin_entries.rs @@ -95,7 +95,7 @@ fn symlink_bin_entry( })?; } let original_relative = - pathdiff::diff_paths(&original, bin_node_modules_dir_path) + crate::util::path::relative_path(bin_node_modules_dir_path, &original) .unwrap_or(original); symlink(&original_relative, &link).with_context(|| { format!( diff --git a/cli/util/path.rs b/cli/util/path.rs index a3109ad04ad9a..1688922510eff 100644 --- a/cli/util/path.rs +++ b/cli/util/path.rs @@ -147,6 +147,10 @@ pub fn path_with_stem_suffix(path: &Path, suffix: &str) -> PathBuf { } } +pub fn relative_path(from: &Path, to: &Path) -> Option { + pathdiff::diff_paths(&to, &from) +} + /// Gets if the provided character is not supported on all /// kinds of file systems. pub fn is_banned_path_char(c: char) -> bool { From 9b2728f65e25121e6c92453505a04e9b91713b51 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Tue, 21 May 2024 16:20:37 -0700 Subject: [PATCH 14/18] Appease clippy --- cli/util/path.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/util/path.rs b/cli/util/path.rs index 1688922510eff..45663719f829f 100644 --- a/cli/util/path.rs +++ b/cli/util/path.rs @@ -148,7 +148,7 @@ pub fn path_with_stem_suffix(path: &Path, suffix: &str) -> PathBuf { } pub fn relative_path(from: &Path, to: &Path) -> Option { - pathdiff::diff_paths(&to, &from) + pathdiff::diff_paths(to, from) } /// Gets if the provided character is not supported on all From 1f685d99dffa990181dbc4c6085d2192156a20a3 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Tue, 21 May 2024 19:41:26 -0700 Subject: [PATCH 15/18] Ignore dead code warning on windows --- cli/util/path.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/cli/util/path.rs b/cli/util/path.rs index 45663719f829f..12e1c23b75241 100644 --- a/cli/util/path.rs +++ b/cli/util/path.rs @@ -147,6 +147,7 @@ pub fn path_with_stem_suffix(path: &Path, suffix: &str) -> PathBuf { } } +#[cfg_attr(windows, allow(dead_code))] pub fn relative_path(from: &Path, to: &Path) -> Option { pathdiff::diff_paths(to, from) } From 05ae47c88688a0005c6794febca51b6da4ef3d1c Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Thu, 23 May 2024 14:38:39 -0700 Subject: [PATCH 16/18] Add test for already set up bin entries --- tests/specs/task/bin_package/__test__.jsonc | 63 ++++++++++++------- .../specs/task/bin_package/already-set-up.out | 9 +++ 2 files changed, 49 insertions(+), 23 deletions(-) create mode 100644 tests/specs/task/bin_package/already-set-up.out diff --git a/tests/specs/task/bin_package/__test__.jsonc b/tests/specs/task/bin_package/__test__.jsonc index bc8321e5dc2ea..6f18ffb952eeb 100644 --- a/tests/specs/task/bin_package/__test__.jsonc +++ b/tests/specs/task/bin_package/__test__.jsonc @@ -1,27 +1,44 @@ { - "tempDir": true, - "steps": [ - // {"commandName": "npm", "args": "install", "output": "\nadded 1 package in [WILDCARD]\n"}, - { - "args": "task sayhi", - "output": "task.out" + "tests": { + "sets_up_bin_dir": { + "tempDir": true, + "steps": [ + // {"commandName": "npm", "args": "install", "output": "\nadded 1 package in [WILDCARD]\n"}, + { + "args": "task sayhi", + "output": "task.out" + }, + { + "if": "unix", + "commandName": "./node_modules/.bin/cli-esm", + "args": "hi hello", + "output": "hi\nhello\n" + }, + { + "if": "windows", + "commandName": "./node_modules/.bin/cli-esm.cmd", + "args": "hi hello", + "output": "hi\nhello\n" + }, + { + "commandName": "npm", + "args": "run sayhi", + "output": "npm-run.out" + } + ] }, - { - "if": "unix", - "commandName": "./node_modules/.bin/cli-esm", - "args": "hi hello", - "output": "hi\nhello\n" - }, - { - "if": "windows", - "commandName": "./node_modules/.bin/cli-esm.cmd", - "args": "hi hello", - "output": "hi\nhello\n" - }, - { - "commandName": "npm", - "args": "run sayhi", - "output": "npm-run.out" + "warns_if_already_setup": { + "tempDir": true, + "steps": [{ + "commandName": "npm", + "args": "install", + "output": "\nadded 1 package in [WILDCARD]\n" + }, + { + "args": "task sayhi", + "output": "already-set-up.out" + } + ] } - ] + } } diff --git a/tests/specs/task/bin_package/already-set-up.out b/tests/specs/task/bin_package/already-set-up.out new file mode 100644 index 0000000000000..336459daa20b3 --- /dev/null +++ b/tests/specs/task/bin_package/already-set-up.out @@ -0,0 +1,9 @@ +Download http://localhost:4260/@denotest/bin +Download http://localhost:4260/@denotest/bin/1.0.0.tgz +Initialize @denotest/bin@1.0.0 +Warning Trying to set up [WILDCARD] bin for [WILDCARD], but an entry pointing to [WILDCARD] already exists. Skipping... +Warning Trying to set up [WILDCARD] bin for [WILDCARD] but an entry pointing to [WILDCARD] already exists. Skipping... +Warning Trying to set up [WILDCARD] bin for [WILDCARD], but an entry pointing to [WILDCARD] already exists. Skipping... +Task sayhi cli-esm hi hello +hi +hello From d5531cc3d6b9c41b67d8c0aac3b3889a699ab7c2 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Thu, 23 May 2024 14:52:29 -0700 Subject: [PATCH 17/18] Fmt --- tests/specs/task/bin_package/__test__.jsonc | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/specs/task/bin_package/__test__.jsonc b/tests/specs/task/bin_package/__test__.jsonc index 6f18ffb952eeb..bdc6bbbd60d74 100644 --- a/tests/specs/task/bin_package/__test__.jsonc +++ b/tests/specs/task/bin_package/__test__.jsonc @@ -33,12 +33,10 @@ "commandName": "npm", "args": "install", "output": "\nadded 1 package in [WILDCARD]\n" - }, - { + }, { "args": "task sayhi", "output": "already-set-up.out" - } - ] + }] } } } From 123e4285a9e02e2741d58344b1ea9f248fe569c1 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Thu, 23 May 2024 15:44:49 -0700 Subject: [PATCH 18/18] Unix only --- tests/specs/task/bin_package/__test__.jsonc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/specs/task/bin_package/__test__.jsonc b/tests/specs/task/bin_package/__test__.jsonc index bdc6bbbd60d74..19bad633e0c47 100644 --- a/tests/specs/task/bin_package/__test__.jsonc +++ b/tests/specs/task/bin_package/__test__.jsonc @@ -30,10 +30,12 @@ "warns_if_already_setup": { "tempDir": true, "steps": [{ + "if": "unix", "commandName": "npm", "args": "install", "output": "\nadded 1 package in [WILDCARD]\n" }, { + "if": "unix", "args": "task sayhi", "output": "already-set-up.out" }]