-
Notifications
You must be signed in to change notification settings - Fork 5.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Handle package.json lifecycle scripts #23558
base: main
Are you sure you want to change the base?
Changes from 11 commits
54e6153
e958340
633a078
fc87931
589a18d
a82d984
f46ab0a
be289c1
8e8fe75
13b613f
f65ffbe
1b0979a
63c9c8c
95b8410
371bb0d
175cdd9
be781f7
6cb958a
267450e
5e4c388
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,8 @@ use std::cmp::Ordering; | |
use std::collections::HashMap; | ||
use std::collections::HashSet; | ||
use std::fs; | ||
use std::io::Write; | ||
use std::os::unix::fs::symlink; | ||
use std::path::Path; | ||
use std::path::PathBuf; | ||
use std::sync::Arc; | ||
|
@@ -24,6 +26,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; | ||
|
@@ -267,6 +270,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"), | ||
|
@@ -291,6 +298,9 @@ 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))); | ||
let packages_with_install_scripts = 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) | ||
|
@@ -319,6 +329,8 @@ 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 packages_with_install_scripts = packages_with_install_scripts.clone(); | ||
let handle = spawn(async move { | ||
cache | ||
.ensure_package(&package.id.nv, &package.dist, ®istry_url) | ||
|
@@ -342,6 +354,15 @@ async fn sync_resolution_with_fs( | |
} | ||
// write out a file that indicates this folder has been initialized | ||
fs::write(initialized_file, "")?; | ||
|
||
if package.bin.is_some() { | ||
bin_entries_to_setup.lock().push((package.clone(), package_path.clone())); | ||
} | ||
|
||
if package.scripts.contains_key("install") || package.scripts.contains_key("postinstall") { | ||
packages_with_install_scripts.lock().push((package.clone(), package_path)); | ||
} | ||
|
||
// finally stop showing the progress bar | ||
drop(pb_guard); // explicit for clarity | ||
Ok(()) | ||
|
@@ -472,6 +493,74 @@ 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, | ||
)?; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
// 7. Run pre/post/install scripts for packages | ||
for (package, package_path) in &*packages_with_install_scripts.lock() { | ||
let package = snapshot.package_from_id(&package.id).unwrap(); | ||
|
||
for (script_name, script) in &package.scripts { | ||
if script_name == "preinstall" || script_name == "install" || script_name == "postinstall" { | ||
log::warn!( | ||
"⚠️ {} {} has a \"{}\" script that might be required to execute for the package to work correctly.", | ||
deno_terminal::colors::yellow("Warning"), | ||
deno_terminal::colors::green(format!("{}", package.id.nv.name)), | ||
deno_terminal::colors::gray(format!("{}", script_name)), | ||
); | ||
log::warn!(" Script: {}", deno_terminal::colors::gray(script)); | ||
// TODO: add a prompt here to ask if we should run the script. | ||
log::warn!(" Do you want to run this script with full permissions? [y/N]"); | ||
// ASCII code for the bell character. | ||
print!("\x07"); | ||
eprint!(" > "); | ||
let mut buf = String::new(); | ||
let mut should_run = false; | ||
loop { | ||
let line_result = std::io::stdin().read_line(&mut buf); | ||
if let Ok(_nread) = line_result { | ||
let answer = buf.trim(); | ||
if answer == "y" || answer == "Y" { | ||
should_run = true; | ||
break; | ||
} else if answer == "n" || answer == "N" { | ||
break; | ||
} | ||
} | ||
} | ||
// if should_run { | ||
|
||
// } | ||
} | ||
} | ||
} | ||
|
||
setup_cache.save(); | ||
drop(single_process_lock); | ||
drop(pb_clear_guard); | ||
|
@@ -648,6 +737,39 @@ fn get_package_folder_id_from_folder_name( | |
}) | ||
} | ||
|
||
fn symlink_bin_entry( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have some code somewhere else that handles doing a symlink like this on unix and will use junctions on windows. It would be good to reuse that code here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or actually, I guess since this is setting up scripts for windows we should have it create something executable here for that. |
||
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, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could a previously executing script stuff stdin? We should maybe re-use the permission prompt code here somehow?