1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-24 15:19:26 -05:00

fix(install): re-setup bin entries after running lifecycle scripts (#26752)

Fixes #26677

Some packages (like supabase) declare bin entries that don't exist until
lifecycle scripts are run. For instance, the lifecycle script downloads
a binary file which serves as a bin entrypoint.

Unfortunately you can't just defer setting up the bin entries until
after lifecycle scripts have run, because the scripts may rely on them.

I looked into this, and PNPM just re-links bin entries after running
lifecycle scripts. I think that's about the best we can do as well.

Note that we'll only re-setup bin entries for packages whose lifecycle
scripts we run. This should limit the performance cost, as typically a
given project will not have many lifecycle scripts (and of those, many
of them probably don't have bin entries to set up).
This commit is contained in:
Nathan Whitaker 2024-11-12 09:23:39 -08:00 committed by GitHub
parent 15b6baff33
commit c371b2a492
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 253 additions and 47 deletions

View file

@ -18,6 +18,7 @@ pub struct BinEntries<'a> {
seen_names: HashMap<&'a str, &'a NpmPackageId>, seen_names: HashMap<&'a str, &'a NpmPackageId>,
/// The bin entries /// The bin entries
entries: Vec<(&'a NpmResolutionPackage, PathBuf)>, entries: Vec<(&'a NpmResolutionPackage, PathBuf)>,
sorted: bool,
} }
/// Returns the name of the default binary for the given package. /// Returns the name of the default binary for the given package.
@ -31,6 +32,20 @@ fn default_bin_name(package: &NpmResolutionPackage) -> &str {
.map_or(package.id.nv.name.as_str(), |(_, name)| name) .map_or(package.id.nv.name.as_str(), |(_, name)| name)
} }
pub fn warn_missing_entrypoint(
bin_name: &str,
package_path: &Path,
entrypoint: &Path,
) {
log::warn!(
"{} Trying to set up '{}' bin for \"{}\", but the entry point \"{}\" doesn't exist.",
deno_terminal::colors::yellow("Warning"),
bin_name,
package_path.display(),
entrypoint.display()
);
}
impl<'a> BinEntries<'a> { impl<'a> BinEntries<'a> {
pub fn new() -> Self { pub fn new() -> Self {
Self::default() Self::default()
@ -42,6 +57,7 @@ impl<'a> BinEntries<'a> {
package: &'a NpmResolutionPackage, package: &'a NpmResolutionPackage,
package_path: PathBuf, package_path: PathBuf,
) { ) {
self.sorted = false;
// check for a new collision, if we haven't already // check for a new collision, if we haven't already
// found one // found one
match package.bin.as_ref().unwrap() { match package.bin.as_ref().unwrap() {
@ -79,16 +95,21 @@ impl<'a> BinEntries<'a> {
&str, // bin name &str, // bin name
&str, // bin script &str, // bin script
) -> Result<(), AnyError>, ) -> Result<(), AnyError>,
mut filter: impl FnMut(&NpmResolutionPackage) -> bool,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
if !self.collisions.is_empty() { if !self.collisions.is_empty() && !self.sorted {
// walking the dependency tree to find out the depth of each package // walking the dependency tree to find out the depth of each package
// is sort of expensive, so we only do it if there's a collision // is sort of expensive, so we only do it if there's a collision
sort_by_depth(snapshot, &mut self.entries, &mut self.collisions); sort_by_depth(snapshot, &mut self.entries, &mut self.collisions);
self.sorted = true;
} }
let mut seen = HashSet::new(); let mut seen = HashSet::new();
for (package, package_path) in &self.entries { for (package, package_path) in &self.entries {
if !filter(package) {
continue;
}
if let Some(bin_entries) = &package.bin { if let Some(bin_entries) = &package.bin {
match bin_entries { match bin_entries {
deno_npm::registry::NpmPackageVersionBinEntry::String(script) => { deno_npm::registry::NpmPackageVersionBinEntry::String(script) => {
@ -118,8 +139,8 @@ impl<'a> BinEntries<'a> {
} }
/// Collect the bin entries into a vec of (name, script path) /// Collect the bin entries into a vec of (name, script path)
pub fn into_bin_files( pub fn collect_bin_files(
mut self, &mut self,
snapshot: &NpmResolutionSnapshot, snapshot: &NpmResolutionSnapshot,
) -> Vec<(String, PathBuf)> { ) -> Vec<(String, PathBuf)> {
let mut bins = Vec::new(); let mut bins = Vec::new();
@ -131,17 +152,18 @@ impl<'a> BinEntries<'a> {
bins.push((name.to_string(), package_path.join(script))); bins.push((name.to_string(), package_path.join(script)));
Ok(()) Ok(())
}, },
|_| true,
) )
.unwrap(); .unwrap();
bins bins
} }
/// Finish setting up the bin entries, writing the necessary files fn set_up_entries_filtered(
/// to disk.
pub fn finish(
mut self, mut self,
snapshot: &NpmResolutionSnapshot, snapshot: &NpmResolutionSnapshot,
bin_node_modules_dir_path: &Path, bin_node_modules_dir_path: &Path,
filter: impl FnMut(&NpmResolutionPackage) -> bool,
mut handler: impl FnMut(&EntrySetupOutcome<'_>),
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
if !self.entries.is_empty() && !bin_node_modules_dir_path.exists() { if !self.entries.is_empty() && !bin_node_modules_dir_path.exists() {
std::fs::create_dir_all(bin_node_modules_dir_path).with_context( std::fs::create_dir_all(bin_node_modules_dir_path).with_context(
@ -160,18 +182,54 @@ impl<'a> BinEntries<'a> {
Ok(()) Ok(())
}, },
|package, package_path, name, script| { |package, package_path, name, script| {
set_up_bin_entry( let outcome = set_up_bin_entry(
package, package,
name, name,
script, script,
package_path, package_path,
bin_node_modules_dir_path, bin_node_modules_dir_path,
) )?;
handler(&outcome);
Ok(())
}, },
filter,
)?; )?;
Ok(()) Ok(())
} }
/// Finish setting up the bin entries, writing the necessary files
/// to disk.
pub fn finish(
self,
snapshot: &NpmResolutionSnapshot,
bin_node_modules_dir_path: &Path,
handler: impl FnMut(&EntrySetupOutcome<'_>),
) -> Result<(), AnyError> {
self.set_up_entries_filtered(
snapshot,
bin_node_modules_dir_path,
|_| true,
handler,
)
}
/// Finish setting up the bin entries, writing the necessary files
/// to disk.
pub fn finish_only(
self,
snapshot: &NpmResolutionSnapshot,
bin_node_modules_dir_path: &Path,
handler: impl FnMut(&EntrySetupOutcome<'_>),
only: &HashSet<&NpmPackageId>,
) -> Result<(), AnyError> {
self.set_up_entries_filtered(
snapshot,
bin_node_modules_dir_path,
|package| only.contains(&package.id),
handler,
)
}
} }
// walk the dependency tree to find out the depth of each package // walk the dependency tree to find out the depth of each package
@ -233,16 +291,17 @@ fn sort_by_depth(
}); });
} }
pub fn set_up_bin_entry( pub fn set_up_bin_entry<'a>(
package: &NpmResolutionPackage, package: &'a NpmResolutionPackage,
bin_name: &str, bin_name: &'a str,
#[allow(unused_variables)] bin_script: &str, #[allow(unused_variables)] bin_script: &str,
#[allow(unused_variables)] package_path: &Path, #[allow(unused_variables)] package_path: &'a Path,
bin_node_modules_dir_path: &Path, bin_node_modules_dir_path: &Path,
) -> Result<(), AnyError> { ) -> Result<EntrySetupOutcome<'a>, AnyError> {
#[cfg(windows)] #[cfg(windows)]
{ {
set_up_bin_shim(package, bin_name, bin_node_modules_dir_path)?; set_up_bin_shim(package, bin_name, bin_node_modules_dir_path)?;
Ok(EntrySetupOutcome::Success)
} }
#[cfg(unix)] #[cfg(unix)]
{ {
@ -252,9 +311,8 @@ pub fn set_up_bin_entry(
bin_script, bin_script,
package_path, package_path,
bin_node_modules_dir_path, bin_node_modules_dir_path,
)?; )
} }
Ok(())
} }
#[cfg(windows)] #[cfg(windows)]
@ -301,14 +359,39 @@ fn make_executable_if_exists(path: &Path) -> Result<bool, AnyError> {
Ok(true) Ok(true)
} }
pub enum EntrySetupOutcome<'a> {
#[cfg_attr(windows, allow(dead_code))]
MissingEntrypoint {
bin_name: &'a str,
package_path: &'a Path,
entrypoint: PathBuf,
package: &'a NpmResolutionPackage,
},
Success,
}
impl<'a> EntrySetupOutcome<'a> {
pub fn warn_if_failed(&self) {
match self {
EntrySetupOutcome::MissingEntrypoint {
bin_name,
package_path,
entrypoint,
..
} => warn_missing_entrypoint(bin_name, package_path, entrypoint),
EntrySetupOutcome::Success => {}
}
}
}
#[cfg(unix)] #[cfg(unix)]
fn symlink_bin_entry( fn symlink_bin_entry<'a>(
_package: &NpmResolutionPackage, package: &'a NpmResolutionPackage,
bin_name: &str, bin_name: &'a str,
bin_script: &str, bin_script: &str,
package_path: &Path, package_path: &'a Path,
bin_node_modules_dir_path: &Path, bin_node_modules_dir_path: &Path,
) -> Result<(), AnyError> { ) -> Result<EntrySetupOutcome<'a>, AnyError> {
use std::io; use std::io;
use std::os::unix::fs::symlink; use std::os::unix::fs::symlink;
let link = bin_node_modules_dir_path.join(bin_name); let link = bin_node_modules_dir_path.join(bin_name);
@ -318,14 +401,12 @@ fn symlink_bin_entry(
format!("Can't set up '{}' bin at {}", bin_name, original.display()) format!("Can't set up '{}' bin at {}", bin_name, original.display())
})?; })?;
if !found { if !found {
log::warn!( return Ok(EntrySetupOutcome::MissingEntrypoint {
"{} Trying to set up '{}' bin for \"{}\", but the entry point \"{}\" doesn't exist.",
deno_terminal::colors::yellow("Warning"),
bin_name, bin_name,
package_path.display(), package_path,
original.display() entrypoint: original,
); package,
return Ok(()); });
} }
let original_relative = let original_relative =
@ -348,7 +429,7 @@ fn symlink_bin_entry(
original_relative.display() original_relative.display()
) )
})?; })?;
return Ok(()); return Ok(EntrySetupOutcome::Success);
} }
return Err(err).with_context(|| { return Err(err).with_context(|| {
format!( format!(
@ -359,5 +440,5 @@ fn symlink_bin_entry(
}); });
} }
Ok(()) Ok(EntrySetupOutcome::Success)
} }

View file

@ -10,6 +10,7 @@ use deno_runtime::deno_io::FromRawIoHandle;
use deno_semver::package::PackageNv; use deno_semver::package::PackageNv;
use deno_semver::Version; use deno_semver::Version;
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::HashSet;
use std::rc::Rc; use std::rc::Rc;
use std::path::Path; use std::path::Path;
@ -61,7 +62,7 @@ impl<'a> LifecycleScripts<'a> {
} }
} }
fn has_lifecycle_scripts( pub fn has_lifecycle_scripts(
package: &NpmResolutionPackage, package: &NpmResolutionPackage,
package_path: &Path, package_path: &Path,
) -> bool { ) -> bool {
@ -83,7 +84,7 @@ fn is_broken_default_install_script(script: &str, package_path: &Path) -> bool {
} }
impl<'a> LifecycleScripts<'a> { impl<'a> LifecycleScripts<'a> {
fn can_run_scripts(&self, package_nv: &PackageNv) -> bool { pub fn can_run_scripts(&self, package_nv: &PackageNv) -> bool {
if !self.strategy.can_run_scripts() { if !self.strategy.can_run_scripts() {
return false; return false;
} }
@ -98,6 +99,9 @@ impl<'a> LifecycleScripts<'a> {
PackagesAllowedScripts::None => false, PackagesAllowedScripts::None => false,
} }
} }
pub fn has_run_scripts(&self, package: &NpmResolutionPackage) -> bool {
self.strategy.has_run(package)
}
/// Register a package for running lifecycle scripts, if applicable. /// Register a package for running lifecycle scripts, if applicable.
/// ///
/// `package_path` is the path containing the package's code (its root dir). /// `package_path` is the path containing the package's code (its root dir).
@ -110,12 +114,12 @@ impl<'a> LifecycleScripts<'a> {
) { ) {
if has_lifecycle_scripts(package, &package_path) { if has_lifecycle_scripts(package, &package_path) {
if self.can_run_scripts(&package.id.nv) { if self.can_run_scripts(&package.id.nv) {
if !self.strategy.has_run(package) { if !self.has_run_scripts(package) {
self self
.packages_with_scripts .packages_with_scripts
.push((package, package_path.into_owned())); .push((package, package_path.into_owned()));
} }
} else if !self.strategy.has_run(package) } else if !self.has_run_scripts(package)
&& (self.config.explicit_install || !self.strategy.has_warned(package)) && (self.config.explicit_install || !self.strategy.has_warned(package))
{ {
// Skip adding `esbuild` as it is known that it can work properly without lifecycle script // Skip adding `esbuild` as it is known that it can work properly without lifecycle script
@ -149,22 +153,32 @@ impl<'a> LifecycleScripts<'a> {
self, self,
snapshot: &NpmResolutionSnapshot, snapshot: &NpmResolutionSnapshot,
packages: &[NpmResolutionPackage], packages: &[NpmResolutionPackage],
root_node_modules_dir_path: Option<&Path>, root_node_modules_dir_path: &Path,
progress_bar: &ProgressBar, progress_bar: &ProgressBar,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
self.warn_not_run_scripts()?; self.warn_not_run_scripts()?;
let get_package_path = let get_package_path =
|p: &NpmResolutionPackage| self.strategy.package_path(p); |p: &NpmResolutionPackage| self.strategy.package_path(p);
let mut failed_packages = Vec::new(); let mut failed_packages = Vec::new();
let mut bin_entries = BinEntries::new();
if !self.packages_with_scripts.is_empty() { if !self.packages_with_scripts.is_empty() {
let package_ids = self
.packages_with_scripts
.iter()
.map(|(p, _)| &p.id)
.collect::<HashSet<_>>();
// get custom commands for each bin available in the node_modules dir (essentially // get custom commands for each bin available in the node_modules dir (essentially
// the scripts that are in `node_modules/.bin`) // the scripts that are in `node_modules/.bin`)
let base = let base = resolve_baseline_custom_commands(
resolve_baseline_custom_commands(snapshot, packages, get_package_path)?; &mut bin_entries,
snapshot,
packages,
get_package_path,
)?;
let init_cwd = &self.config.initial_cwd; let init_cwd = &self.config.initial_cwd;
let process_state = crate::npm::managed::npm_process_state( let process_state = crate::npm::managed::npm_process_state(
snapshot.as_valid_serialized(), snapshot.as_valid_serialized(),
root_node_modules_dir_path, Some(root_node_modules_dir_path),
); );
let mut env_vars = crate::task_runner::real_env_vars(); let mut env_vars = crate::task_runner::real_env_vars();
@ -221,7 +235,7 @@ impl<'a> LifecycleScripts<'a> {
custom_commands: custom_commands.clone(), custom_commands: custom_commands.clone(),
init_cwd, init_cwd,
argv: &[], argv: &[],
root_node_modules_dir: root_node_modules_dir_path, root_node_modules_dir: Some(root_node_modules_dir_path),
stdio: Some(crate::task_runner::TaskIo { stdio: Some(crate::task_runner::TaskIo {
stderr: TaskStdio::piped(), stderr: TaskStdio::piped(),
stdout: TaskStdio::piped(), stdout: TaskStdio::piped(),
@ -262,6 +276,17 @@ impl<'a> LifecycleScripts<'a> {
} }
self.strategy.did_run_scripts(package)?; self.strategy.did_run_scripts(package)?;
} }
// re-set up bin entries for the packages which we've run scripts for.
// lifecycle scripts can create files that are linked to by bin entries,
// and the only reliable way to handle this is to re-link bin entries
// (this is what PNPM does as well)
bin_entries.finish_only(
snapshot,
&root_node_modules_dir_path.join(".bin"),
|outcome| outcome.warn_if_failed(),
&package_ids,
)?;
} }
if failed_packages.is_empty() { if failed_packages.is_empty() {
Ok(()) Ok(())
@ -281,9 +306,10 @@ impl<'a> LifecycleScripts<'a> {
// take in all (non copy) packages from snapshot, // take in all (non copy) packages from snapshot,
// and resolve the set of available binaries to create // and resolve the set of available binaries to create
// custom commands available to the task runner // custom commands available to the task runner
fn resolve_baseline_custom_commands( fn resolve_baseline_custom_commands<'a>(
snapshot: &NpmResolutionSnapshot, bin_entries: &mut BinEntries<'a>,
packages: &[NpmResolutionPackage], snapshot: &'a NpmResolutionSnapshot,
packages: &'a [NpmResolutionPackage],
get_package_path: impl Fn(&NpmResolutionPackage) -> PathBuf, get_package_path: impl Fn(&NpmResolutionPackage) -> PathBuf,
) -> Result<crate::task_runner::TaskCustomCommands, AnyError> { ) -> Result<crate::task_runner::TaskCustomCommands, AnyError> {
let mut custom_commands = crate::task_runner::TaskCustomCommands::new(); let mut custom_commands = crate::task_runner::TaskCustomCommands::new();
@ -306,6 +332,7 @@ fn resolve_baseline_custom_commands(
// doing it for packages that are set up already. // doing it for packages that are set up already.
// realistically, scripts won't be run very often so it probably isn't too big of an issue. // realistically, scripts won't be run very often so it probably isn't too big of an issue.
resolve_custom_commands_from_packages( resolve_custom_commands_from_packages(
bin_entries,
custom_commands, custom_commands,
snapshot, snapshot,
packages, packages,
@ -320,12 +347,12 @@ fn resolve_custom_commands_from_packages<
'a, 'a,
P: IntoIterator<Item = &'a NpmResolutionPackage>, P: IntoIterator<Item = &'a NpmResolutionPackage>,
>( >(
bin_entries: &mut BinEntries<'a>,
mut commands: crate::task_runner::TaskCustomCommands, mut commands: crate::task_runner::TaskCustomCommands,
snapshot: &'a NpmResolutionSnapshot, snapshot: &'a NpmResolutionSnapshot,
packages: P, packages: P,
get_package_path: impl Fn(&'a NpmResolutionPackage) -> PathBuf, get_package_path: impl Fn(&'a NpmResolutionPackage) -> PathBuf,
) -> Result<crate::task_runner::TaskCustomCommands, AnyError> { ) -> Result<crate::task_runner::TaskCustomCommands, AnyError> {
let mut bin_entries = BinEntries::new();
for package in packages { for package in packages {
let package_path = get_package_path(package); let package_path = get_package_path(package);
@ -333,7 +360,7 @@ fn resolve_custom_commands_from_packages<
bin_entries.add(package, package_path); bin_entries.add(package, package_path);
} }
} }
let bins = bin_entries.into_bin_files(snapshot); let bins: Vec<(String, PathBuf)> = bin_entries.collect_bin_files(snapshot);
for (bin_name, script_path) in bins { for (bin_name, script_path) in bins {
commands.insert( commands.insert(
bin_name.clone(), bin_name.clone(),
@ -356,7 +383,9 @@ fn resolve_custom_commands_from_deps(
snapshot: &NpmResolutionSnapshot, snapshot: &NpmResolutionSnapshot,
get_package_path: impl Fn(&NpmResolutionPackage) -> PathBuf, get_package_path: impl Fn(&NpmResolutionPackage) -> PathBuf,
) -> Result<crate::task_runner::TaskCustomCommands, AnyError> { ) -> Result<crate::task_runner::TaskCustomCommands, AnyError> {
let mut bin_entries = BinEntries::new();
resolve_custom_commands_from_packages( resolve_custom_commands_from_packages(
&mut bin_entries,
baseline, baseline,
snapshot, snapshot,
package package

View file

@ -55,6 +55,7 @@ use crate::util::progress_bar::ProgressMessagePrompt;
use super::super::cache::NpmCache; use super::super::cache::NpmCache;
use super::super::cache::TarballCache; use super::super::cache::TarballCache;
use super::super::resolution::NpmResolution; use super::super::resolution::NpmResolution;
use super::common::bin_entries;
use super::common::NpmPackageFsResolver; use super::common::NpmPackageFsResolver;
use super::common::RegistryReadPermissionChecker; use super::common::RegistryReadPermissionChecker;
@ -329,8 +330,7 @@ async fn sync_resolution_with_fs(
let mut cache_futures = FuturesUnordered::new(); let mut cache_futures = FuturesUnordered::new();
let mut newest_packages_by_name: HashMap<&String, &NpmResolutionPackage> = let mut newest_packages_by_name: HashMap<&String, &NpmResolutionPackage> =
HashMap::with_capacity(package_partitions.packages.len()); HashMap::with_capacity(package_partitions.packages.len());
let bin_entries = let bin_entries = Rc::new(RefCell::new(bin_entries::BinEntries::new()));
Rc::new(RefCell::new(super::common::bin_entries::BinEntries::new()));
let mut lifecycle_scripts = let mut lifecycle_scripts =
super::common::lifecycle_scripts::LifecycleScripts::new( super::common::lifecycle_scripts::LifecycleScripts::new(
lifecycle_scripts, lifecycle_scripts,
@ -658,7 +658,28 @@ async fn sync_resolution_with_fs(
// 7. Set up `node_modules/.bin` entries for packages that need it. // 7. Set up `node_modules/.bin` entries for packages that need it.
{ {
let bin_entries = std::mem::take(&mut *bin_entries.borrow_mut()); let bin_entries = std::mem::take(&mut *bin_entries.borrow_mut());
bin_entries.finish(snapshot, &bin_node_modules_dir_path)?; bin_entries.finish(
snapshot,
&bin_node_modules_dir_path,
|setup_outcome| {
match setup_outcome {
bin_entries::EntrySetupOutcome::MissingEntrypoint {
package,
package_path,
..
} if super::common::lifecycle_scripts::has_lifecycle_scripts(
package,
package_path,
) && lifecycle_scripts.can_run_scripts(&package.id.nv)
&& !lifecycle_scripts.has_run_scripts(package) =>
{
// ignore, it might get fixed when the lifecycle scripts run.
// if not, we'll warn then
}
outcome => outcome.warn_if_failed(),
}
},
)?;
} }
// 8. Create symlinks for the workspace packages // 8. Create symlinks for the workspace packages
@ -708,7 +729,7 @@ async fn sync_resolution_with_fs(
.finish( .finish(
snapshot, snapshot,
&package_partitions.packages, &package_partitions.packages,
Some(root_node_modules_dir_path), root_node_modules_dir_path,
progress_bar, progress_bar,
) )
.await?; .await?;

View file

@ -0,0 +1,3 @@
import * as fs from "node:fs";
fs.writeFileSync("./testbin.js", "#!/usr/bin/env node\nconsole.log('run testbin');");

View file

@ -0,0 +1,10 @@
{
"name": "@denotest/bin-created-by-lifecycle",
"version": "1.0.0",
"scripts": {
"install": "node install.mjs"
},
"bin": {
"testbin": "testbin.js"
}
}

View file

@ -0,0 +1,29 @@
{
"tempDir": true,
"tests": {
"all_at_once": {
"steps": [
{
"args": "install --allow-scripts",
"output": "all_at_once_install.out"
},
{ "args": "task run-testbin", "output": "run_testbin.out" }
]
},
"separate_steps": {
"steps": [
{ "if": "unix", "args": "install", "output": "install_warn.out" },
{
"if": "windows",
"args": "install",
"output": "install_warn_windows.out"
},
{
"args": "install --allow-scripts",
"output": "Initialize @denotest/bin-created-by-lifecycle@1.0.0: running 'install' script\n"
},
{ "args": "task run-testbin", "output": "run_testbin.out" }
]
}
}
}

View file

@ -0,0 +1,4 @@
Download http://localhost:4260/@denotest%2fbin-created-by-lifecycle
Download http://localhost:4260/@denotest/bin-created-by-lifecycle/1.0.0.tgz
Initialize @denotest/bin-created-by-lifecycle@1.0.0
Initialize @denotest/bin-created-by-lifecycle@1.0.0: running 'install' script

View file

@ -0,0 +1,10 @@
Download http://localhost:4260/@denotest%2fbin-created-by-lifecycle
Download http://localhost:4260/@denotest/bin-created-by-lifecycle/1.0.0.tgz
Initialize @denotest/bin-created-by-lifecycle@1.0.0
Warning Trying to set up 'testbin' bin for "[WILDCARD]bin-created-by-lifecycle", but the entry point "[WILDCARD]testbin.js" doesn't exist.
Warning The following packages contained npm lifecycle scripts (preinstall/install/postinstall) that were not executed:
┠─ npm:@denotest/bin-created-by-lifecycle@1.0.0
┠─ This may cause the packages to not work correctly.
┖─ To run lifecycle scripts, use the `--allow-scripts` flag with `deno install`:
deno install --allow-scripts=npm:@denotest/bin-created-by-lifecycle@1.0.0

View file

@ -0,0 +1,9 @@
Download http://localhost:4260/@denotest%2fbin-created-by-lifecycle
Download http://localhost:4260/@denotest/bin-created-by-lifecycle/1.0.0.tgz
Initialize @denotest/bin-created-by-lifecycle@1.0.0
Warning The following packages contained npm lifecycle scripts (preinstall/install/postinstall) that were not executed:
┠─ npm:@denotest/bin-created-by-lifecycle@1.0.0
┠─ This may cause the packages to not work correctly.
┖─ To run lifecycle scripts, use the `--allow-scripts` flag with `deno install`:
deno install --allow-scripts=npm:@denotest/bin-created-by-lifecycle@1.0.0

View file

@ -0,0 +1,8 @@
{
"dependencies": {
"@denotest/bin-created-by-lifecycle": "1.0.0"
},
"scripts": {
"run-testbin": "testbin"
}
}

View file

@ -0,0 +1,2 @@
Task run-testbin testbin
run testbin