mirror of
https://github.com/denoland/deno.git
synced 2025-01-21 21:50:00 -05:00
feat(npm): support running non-bin scripts in npm pkgs via deno run
(#19975)
Closes https://github.com/denoland/deno/issues/19967
This commit is contained in:
parent
51ceed860b
commit
00b5fe8ba3
7 changed files with 110 additions and 1 deletions
|
@ -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",
|
||||
|
|
1
cli/tests/testdata/npm/deno_run_no_bin_entrypoint.out
vendored
Normal file
1
cli/tests/testdata/npm/deno_run_no_bin_entrypoint.out
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
error: package '@denotest/esm-basic' did not have a bin property in its package.json
|
3
cli/tests/testdata/npm/deno_run_no_bin_entrypoint_non_existent_subpath.out
vendored
Normal file
3
cli/tests/testdata/npm/deno_run_no_bin_entrypoint_non_existent_subpath.out
vendored
Normal file
|
@ -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'
|
5
cli/tests/testdata/npm/registry/@denotest/bin/0.6.0/cli-cjs.js
vendored
Normal file
5
cli/tests/testdata/npm/registry/@denotest/bin/0.6.0/cli-cjs.js
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
const process = require("process");
|
||||
|
||||
for (const arg of process.argv.slice(2)) {
|
||||
console.log(arg);
|
||||
}
|
5
cli/tests/testdata/npm/registry/@denotest/bin/0.6.0/cli.mjs
vendored
Normal file
5
cli/tests/testdata/npm/registry/@denotest/bin/0.6.0/cli.mjs
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
import process from "node:process";
|
||||
|
||||
for (const arg of process.argv.slice(2)) {
|
||||
console.log(arg);
|
||||
}
|
4
cli/tests/testdata/npm/registry/@denotest/bin/0.6.0/package.json
vendored
Normal file
4
cli/tests/testdata/npm/registry/@denotest/bin/0.6.0/package.json
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"name": "@deno/bin",
|
||||
"version": "0.6.0"
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue