Skip to content

Commit 861f62a

Browse files
feat: add deno x (alias dx) for conveniently running binaries from packages (#31138)
an equivalent to npx, for conveniently running binaries from packages available as `deno x`, and you can install an alias via `deno x --install-alias` (defaults to dx but can be specified) Used like `deno x rolldown`, or `dx rolldown` with the alias - defaults to allow-all, unless another permission flag is passed - prompts when you first run a package - runs lifecycle scripts if you accept the prompt - defaults to npm: unless otherwise specified - errors if you try to use it to run a file --------- Co-authored-by: Bartek Iwańczuk <biwanczuk@gmail.com>
1 parent f5cb510 commit 861f62a

File tree

32 files changed

+979
-76
lines changed

32 files changed

+979
-76
lines changed

cli/args/flags.rs

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,42 @@ impl RunFlags {
417417
}
418418
}
419419

420+
#[derive(Clone, Debug, Eq, PartialEq, Default)]
421+
pub enum DenoXShimName {
422+
#[default]
423+
Dx,
424+
Denox,
425+
Other(String),
426+
}
427+
428+
impl DenoXShimName {
429+
pub fn name(&self) -> &str {
430+
match self {
431+
Self::Dx => "dx",
432+
Self::Denox => "denox",
433+
Self::Other(name) => name,
434+
}
435+
}
436+
}
437+
438+
#[derive(Clone, Debug, Eq, PartialEq)]
439+
pub enum XFlagsKind {
440+
InstallAlias(DenoXShimName),
441+
Command(XCommandFlags),
442+
Print,
443+
}
444+
445+
#[derive(Clone, Debug, Eq, PartialEq)]
446+
pub struct XCommandFlags {
447+
pub yes: bool,
448+
pub command: String,
449+
}
450+
451+
#[derive(Clone, Debug, Eq, PartialEq)]
452+
pub struct XFlags {
453+
pub kind: XFlagsKind,
454+
}
455+
420456
#[derive(Clone, Debug, Eq, PartialEq)]
421457
pub struct ServeFlags {
422458
pub script: String,
@@ -585,6 +621,7 @@ pub enum DenoSubcommand {
585621
Vendor,
586622
Publish(PublishFlags),
587623
Help(HelpFlags),
624+
X(XFlags),
588625
}
589626

590627
impl DenoSubcommand {
@@ -1584,6 +1621,20 @@ pub fn flags_from_vec_with_initial_cwd(
15841621
args: Vec<OsString>,
15851622
initial_cwd: Option<PathBuf>,
15861623
) -> clap::error::Result<Flags> {
1624+
let args = if !args.is_empty()
1625+
&& (args[0].as_encoded_bytes().ends_with(b"dx")
1626+
|| args[0].as_encoded_bytes().ends_with(b"denox"))
1627+
{
1628+
let mut new_args = Vec::with_capacity(args.len() + 1);
1629+
new_args.push(args[0].clone());
1630+
new_args.push(OsString::from("x"));
1631+
if args.len() >= 2 {
1632+
new_args.extend(args.into_iter().skip(1));
1633+
}
1634+
new_args
1635+
} else {
1636+
args
1637+
};
15871638
let mut app = clap_root();
15881639
let mut matches =
15891640
app
@@ -1766,6 +1817,7 @@ pub fn flags_from_vec_with_initial_cwd(
17661817
"upgrade" => upgrade_parse(&mut flags, &mut m),
17671818
"vendor" => vendor_parse(&mut flags, &mut m),
17681819
"publish" => publish_parse(&mut flags, &mut m)?,
1820+
"x" => x_parse(&mut flags, &mut m)?,
17691821
_ => unreachable!(),
17701822
}
17711823
}
@@ -2028,7 +2080,8 @@ pub fn clap_root() -> Command {
20282080
.subcommand(types_subcommand())
20292081
.subcommand(update_subcommand())
20302082
.subcommand(upgrade_subcommand())
2031-
.subcommand(vendor_subcommand());
2083+
.subcommand(vendor_subcommand())
2084+
.subcommand(x_subcommand());
20322085

20332086
let help = help_subcommand(&cmd);
20342087
cmd.subcommand(help)
@@ -3548,6 +3601,46 @@ The installation root is determined, in order of precedence:
35483601
})
35493602
}
35503603

3604+
fn deno_x_shim_name_parser(value: &str) -> Result<DenoXShimName, String> {
3605+
match value {
3606+
"dx" => Ok(DenoXShimName::Dx),
3607+
"denox" => Ok(DenoXShimName::Denox),
3608+
_ => Ok(DenoXShimName::Other(value.to_string())),
3609+
}
3610+
}
3611+
3612+
fn x_subcommand() -> Command {
3613+
command(
3614+
"x",
3615+
cstr!("Execute a binary from npm or jsr, like npx"),
3616+
UnstableArgsConfig::ResolutionAndRuntime,
3617+
)
3618+
.defer(|cmd| {
3619+
runtime_args(cmd, true, true, true)
3620+
.arg(script_arg().trailing_var_arg(true))
3621+
.arg(
3622+
Arg::new("yes")
3623+
.long("yes")
3624+
.short('y')
3625+
.help("Assume confirmation for all prompts")
3626+
.action(ArgAction::SetTrue)
3627+
.conflicts_with("install-alias"),
3628+
)
3629+
.arg(check_arg(false))
3630+
.arg(env_file_arg())
3631+
.arg(
3632+
Arg::new("install-alias")
3633+
.long("install-alias")
3634+
.num_args(0..=1)
3635+
.default_missing_value("dx")
3636+
.require_equals(true)
3637+
.value_parser(deno_x_shim_name_parser)
3638+
.action(ArgAction::Set)
3639+
.conflicts_with("script_arg"),
3640+
)
3641+
})
3642+
}
3643+
35513644
fn lsp_subcommand() -> Command {
35523645
Command::new("lsp").about(
35533646
"The 'deno lsp' subcommand provides a way for code editors and IDEs to interact with Deno
@@ -6760,6 +6853,35 @@ fn compile_args_without_check_parse(
67606853
Ok(())
67616854
}
67626855

6856+
fn x_parse(
6857+
flags: &mut Flags,
6858+
matches: &mut ArgMatches,
6859+
) -> clap::error::Result<()> {
6860+
let kind = if let Some(shim_name) =
6861+
matches.remove_one::<DenoXShimName>("install-alias")
6862+
{
6863+
XFlagsKind::InstallAlias(shim_name)
6864+
} else if let Some(mut script_arg) =
6865+
matches.remove_many::<String>("script_arg")
6866+
{
6867+
if let Some(command) = script_arg.next() {
6868+
let yes = matches.get_flag("yes");
6869+
flags.argv.extend(script_arg);
6870+
runtime_args_parse(flags, matches, true, true, true)?;
6871+
XFlagsKind::Command(XCommandFlags { yes, command })
6872+
} else {
6873+
XFlagsKind::Print
6874+
}
6875+
} else {
6876+
XFlagsKind::Print
6877+
};
6878+
if !flags.permissions.has_permission() && flags.permission_set.is_none() {
6879+
flags.permissions.allow_all = true;
6880+
}
6881+
flags.subcommand = DenoSubcommand::X(XFlags { kind });
6882+
Ok(())
6883+
}
6884+
67636885
fn escape_and_split_commas(s: String) -> Result<Vec<String>, clap::Error> {
67646886
let mut result = vec![];
67656887
let mut current = String::new();

cli/lib/worker.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -745,7 +745,7 @@ impl<TSys: DenoLibSys> LibMainWorkerFactory<TSys> {
745745
.node_resolver
746746
.resolve_binary_export(package_folder, sub_path)
747747
{
748-
Ok(path) => Ok(url_from_file_path(&path)?),
748+
Ok(bin_value) => Ok(url_from_file_path(bin_value.path())?),
749749
Err(original_err) => {
750750
// if the binary entrypoint was not found, fallback to regular node resolution
751751
let result =

cli/main.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,9 @@ async fn run_subcommand(
257257
DenoSubcommand::Repl(repl_flags) => {
258258
spawn_subcommand(async move { tools::repl::run(flags, repl_flags).await })
259259
}
260+
DenoSubcommand::X(x_flags) => spawn_subcommand(async move {
261+
tools::x::run(flags, x_flags, unconfigured_runtime, roots).await
262+
}),
260263
DenoSubcommand::Run(run_flags) => spawn_subcommand(async move {
261264
if run_flags.print_task_list {
262265
let task_flags = TaskFlags {

cli/task_runner.rs

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,9 @@ impl ShellCommand for NpmCommand {
245245
);
246246
return ExecutableCommand::new(
247247
"deno".to_string(),
248-
std::env::current_exe().unwrap(),
248+
std::env::current_exe()
249+
.and_then(|p| p.canonicalize())
250+
.unwrap(),
249251
)
250252
.execute(ShellCommandContext {
251253
args,
@@ -274,7 +276,9 @@ impl Default for DenoCommand {
274276
fn default() -> Self {
275277
Self(ExecutableCommand::new(
276278
"deno".to_string(),
277-
std::env::current_exe().unwrap(),
279+
std::env::current_exe()
280+
.and_then(|p| p.canonicalize())
281+
.unwrap(),
278282
))
279283
}
280284
}
@@ -323,12 +327,17 @@ impl ShellCommand for NodeCommand {
323327
OsStr::new(USE_PKG_JSON_HIDDEN_ENV_VAR_NAME),
324328
OsStr::new("1"),
325329
);
326-
ExecutableCommand::new("deno".to_string(), std::env::current_exe().unwrap())
327-
.execute(ShellCommandContext {
328-
args,
329-
state,
330-
..context
331-
})
330+
ExecutableCommand::new(
331+
"deno".to_string(),
332+
std::env::current_exe()
333+
.and_then(|p| p.canonicalize())
334+
.unwrap(),
335+
)
336+
.execute(ShellCommandContext {
337+
args,
338+
state,
339+
..context
340+
})
332341
}
333342
}
334343

@@ -418,7 +427,9 @@ impl ShellCommand for NodeModulesFileRunCommand {
418427
args.extend(context.args);
419428
let executable_command = deno_task_shell::ExecutableCommand::new(
420429
"deno".to_string(),
421-
std::env::current_exe().unwrap(),
430+
std::env::current_exe()
431+
.and_then(|p| p.canonicalize())
432+
.unwrap(),
422433
);
423434
// set this environment variable so that the launched process knows the npm command name
424435
context.state.apply_env_var(
@@ -457,8 +468,10 @@ pub fn resolve_npm_commands_from_bin_dir(
457468
.map(|(command_name, path)| {
458469
(
459470
command_name.clone(),
460-
Rc::new(NodeModulesFileRunCommand { command_name, path })
461-
as Rc<dyn ShellCommand>,
471+
Rc::new(NodeModulesFileRunCommand {
472+
command_name,
473+
path: path.path().to_path_buf(),
474+
}) as Rc<dyn ShellCommand>,
462475
)
463476
})
464477
.collect()
@@ -476,8 +489,10 @@ fn resolve_managed_npm_commands(
476489
result.extend(bins.into_iter().map(|(command_name, path)| {
477490
(
478491
command_name.clone(),
479-
Rc::new(NodeModulesFileRunCommand { command_name, path })
480-
as Rc<dyn ShellCommand>,
492+
Rc::new(NodeModulesFileRunCommand {
493+
command_name,
494+
path: path.path().to_path_buf(),
495+
}) as Rc<dyn ShellCommand>,
481496
)
482497
}));
483498
}

cli/tools/bundle/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1748,7 +1748,7 @@ fn resolve_roots(
17481748
let package_folder = npm_resolver
17491749
.resolve_pkg_folder_from_deno_module_req(v.req(), &referrer)
17501750
.unwrap();
1751-
let Ok(main_module) =
1751+
let Ok(node_resolver::BinValue::JsFile(main_module)) =
17521752
node_resolver.resolve_binary_export(&package_folder, v.sub_path())
17531753
else {
17541754
roots.push(url);

cli/tools/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ pub mod serve;
2222
pub mod task;
2323
pub mod test;
2424
pub mod upgrade;
25+
pub mod x;

cli/tools/run/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ To grant permissions, set them before the script argument. For example:
4242
}
4343
}
4444

45-
fn set_npm_user_agent() {
45+
pub fn set_npm_user_agent() {
4646
static ONCE: std::sync::Once = std::sync::Once::new();
4747
ONCE.call_once(|| {
4848
#[allow(clippy::undocumented_unsafe_blocks)]

0 commit comments

Comments
 (0)