summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Sherret <dsherret@users.noreply.github.com>2023-08-01 18:47:57 -0400
committerGitHub <noreply@github.com>2023-08-01 18:47:57 -0400
commit00b5fe8ba31240f6e6abf668ebda8ed0c40fb524 (patch)
tree466a5940ef057e616a63a3d6936f0d31cd48d2b4
parent51ceed860b6f562c70fb09ae1c7173a9eb14473c (diff)
feat(npm): support running non-bin scripts in npm pkgs via `deno run` (#19975)
Closes https://github.com/denoland/deno/issues/19967
-rw-r--r--cli/tests/integration/npm_tests.rs30
-rw-r--r--cli/tests/testdata/npm/deno_run_no_bin_entrypoint.out1
-rw-r--r--cli/tests/testdata/npm/deno_run_no_bin_entrypoint_non_existent_subpath.out3
-rw-r--r--cli/tests/testdata/npm/registry/@denotest/bin/0.6.0/cli-cjs.js5
-rw-r--r--cli/tests/testdata/npm/registry/@denotest/bin/0.6.0/cli.mjs5
-rw-r--r--cli/tests/testdata/npm/registry/@denotest/bin/0.6.0/package.json4
-rw-r--r--cli/worker.rs63
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