diff options
7 files changed, 110 insertions, 1 deletions
diff --git a/cli/tests/integration/npm_tests.rs b/cli/tests/integration/npm_tests.rs index a16dd5354..ddb815a15 100644 --- a/cli/tests/integration/npm_tests.rs +++ b/cli/tests/integration/npm_tests.rs @@ -775,6 +775,20 @@ itest!(deno_run_bin_esm { http_server: true, }); +itest!(deno_run_bin_esm_no_bin_entrypoint { + args: "run -A --quiet npm:@denotest/bin@0.6.0/cli.mjs this is a test", + output: "npm/deno_run_esm.out", + envs: env_vars_for_npm_tests(), + http_server: true, +}); + +itest!(deno_run_bin_cjs_no_bin_entrypoint { + args: "run -A --quiet npm:@denotest/bin@0.6.0/cli-cjs.js this is a test", + output: "npm/deno_run_cjs.out", + envs: env_vars_for_npm_tests(), + http_server: true, +}); + itest!(deno_run_bin_special_chars { args: "run -A --quiet npm:@denotest/special-chars-in-bin-name/\\foo\" this is a test", output: "npm/deno_run_special_chars_in_bin_name.out", @@ -817,6 +831,22 @@ itest!(deno_run_non_existent { exit_code: 1, }); +itest!(deno_run_no_bin_entrypoint { + args: "run -A --quiet npm:@denotest/esm-basic", + output: "npm/deno_run_no_bin_entrypoint.out", + envs: env_vars_for_npm_tests(), + http_server: true, + exit_code: 1, +}); + +itest!(deno_run_no_bin_entrypoint_non_existent_subpath { + args: "run -A --quiet npm:@denotest/esm-basic/non-existent.js", + output: "npm/deno_run_no_bin_entrypoint_non_existent_subpath.out", + envs: env_vars_for_npm_tests(), + http_server: true, + exit_code: 1, +}); + itest!(directory_import_folder_index_js { args: "run npm/directory_import/folder_index_js.ts", output: "npm/directory_import/folder_index_js.out", diff --git a/cli/tests/testdata/npm/deno_run_no_bin_entrypoint.out b/cli/tests/testdata/npm/deno_run_no_bin_entrypoint.out new file mode 100644 index 000000000..b878bc70c --- /dev/null +++ b/cli/tests/testdata/npm/deno_run_no_bin_entrypoint.out @@ -0,0 +1 @@ +error: package '@denotest/esm-basic' did not have a bin property in its package.json diff --git a/cli/tests/testdata/npm/deno_run_no_bin_entrypoint_non_existent_subpath.out b/cli/tests/testdata/npm/deno_run_no_bin_entrypoint_non_existent_subpath.out new file mode 100644 index 000000000..06d7292b0 --- /dev/null +++ b/cli/tests/testdata/npm/deno_run_no_bin_entrypoint_non_existent_subpath.out @@ -0,0 +1,3 @@ +error: package '@denotest/esm-basic' did not have a bin property in its package.json + +Fallback failed: Cannot find module 'file:///[WILDCARD]/non-existent.js' diff --git a/cli/tests/testdata/npm/registry/@denotest/bin/0.6.0/cli-cjs.js b/cli/tests/testdata/npm/registry/@denotest/bin/0.6.0/cli-cjs.js new file mode 100644 index 000000000..7b6ba2724 --- /dev/null +++ b/cli/tests/testdata/npm/registry/@denotest/bin/0.6.0/cli-cjs.js @@ -0,0 +1,5 @@ +const process = require("process"); + +for (const arg of process.argv.slice(2)) { + console.log(arg); +} diff --git a/cli/tests/testdata/npm/registry/@denotest/bin/0.6.0/cli.mjs b/cli/tests/testdata/npm/registry/@denotest/bin/0.6.0/cli.mjs new file mode 100644 index 000000000..0ae8e9190 --- /dev/null +++ b/cli/tests/testdata/npm/registry/@denotest/bin/0.6.0/cli.mjs @@ -0,0 +1,5 @@ +import process from "node:process"; + +for (const arg of process.argv.slice(2)) { + console.log(arg); +} diff --git a/cli/tests/testdata/npm/registry/@denotest/bin/0.6.0/package.json b/cli/tests/testdata/npm/registry/@denotest/bin/0.6.0/package.json new file mode 100644 index 000000000..db50464bc --- /dev/null +++ b/cli/tests/testdata/npm/registry/@denotest/bin/0.6.0/package.json @@ -0,0 +1,4 @@ +{ + "name": "@deno/bin", + "version": "0.6.0" +} diff --git a/cli/worker.rs b/cli/worker.rs index 1516c58da..235e9a225 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -5,6 +5,7 @@ use std::rc::Rc; use std::sync::Arc; use deno_ast::ModuleSpecifier; +use deno_core::anyhow::bail; use deno_core::anyhow::Context; use deno_core::error::AnyError; use deno_core::futures::task::LocalFutureObj; @@ -24,6 +25,7 @@ use deno_runtime::deno_broadcast_channel::InMemoryBroadcastChannel; use deno_runtime::deno_fs; use deno_runtime::deno_node; use deno_runtime::deno_node::NodeResolution; +use deno_runtime::deno_node::NodeResolutionMode; use deno_runtime::deno_node::NodeResolver; use deno_runtime::deno_tls::RootCertStoreProvider; use deno_runtime::deno_web::BlobStore; @@ -370,7 +372,7 @@ impl CliMainWorkerFactory { .add_package_reqs(&[package_ref.req.clone()]) .await?; let node_resolution = - shared.node_resolver.resolve_binary_export(&package_ref)?; + self.resolve_binary_entrypoint(&package_ref, &permissions)?; let is_main_cjs = matches!(node_resolution, NodeResolution::CommonJs(_)); if let Some(lockfile) = &shared.maybe_lockfile { @@ -492,6 +494,65 @@ impl CliMainWorkerFactory { shared: shared.clone(), }) } + + fn resolve_binary_entrypoint( + &self, + package_ref: &NpmPackageReqReference, + permissions: &PermissionsContainer, + ) -> Result<NodeResolution, AnyError> { + match self.shared.node_resolver.resolve_binary_export(package_ref) { + Ok(node_resolution) => Ok(node_resolution), + Err(original_err) => { + // if the binary entrypoint was not found, fallback to regular node resolution + let result = + self.resolve_binary_entrypoint_fallback(package_ref, permissions); + match result { + Ok(Some(resolution)) => Ok(resolution), + Ok(None) => Err(original_err), + Err(fallback_err) => { + bail!("{:#}\n\nFallback failed: {:#}", original_err, fallback_err) + } + } + } + } + } + + /// resolve the binary entrypoint using regular node resolution + fn resolve_binary_entrypoint_fallback( + &self, + package_ref: &NpmPackageReqReference, + permissions: &PermissionsContainer, + ) -> Result<Option<NodeResolution>, AnyError> { + // only fallback if the user specified a sub path + if package_ref.sub_path.is_none() { + // it's confusing to users if the package doesn't have any binary + // entrypoint and we just execute the main script which will likely + // have blank output, so do not resolve the entrypoint in this case + return Ok(None); + } + + let Some(resolution) = self.shared.node_resolver.resolve_npm_req_reference( + package_ref, + NodeResolutionMode::Execution, + permissions, + )? else { + return Ok(None); + }; + match &resolution { + NodeResolution::BuiltIn(_) => Ok(None), + NodeResolution::CommonJs(specifier) | NodeResolution::Esm(specifier) => { + if specifier + .to_file_path() + .map(|p| p.exists()) + .unwrap_or(false) + { + Ok(Some(resolution)) + } else { + bail!("Cannot find module '{}'", specifier) + } + } + } + } } // TODO(bartlomieju): this callback could have default value |