Skip to content

Commit

Permalink
FUTURE: deno install changes (#23498)
Browse files Browse the repository at this point in the history
This PR implements the changes we plan to make to `deno install` in deno
2.0.
- `deno install` without arguments caches dependencies from
`package.json` / `deno.json` and sets up the `node_modules` folder
- `deno install <pkg>` adds the package to the config file (either
`package.json` or `deno.json`), i.e. it aliases `deno add`
- `deno add` can also add deps to `package.json` (this is gated behind
`DENO_FUTURE` due to uncertainty around handling projects with both
`deno.json` and `package.json`)
- `deno install -g <bin>` installs a package as a globally available
binary (the same as `deno install <bin>` in 1.0)

---------

Co-authored-by: Nathan Whitaker <nathan@deno.com>
  • Loading branch information
bartlomieju and nathanwhit committed May 8, 2024
1 parent 525b3c8 commit 4e23a5b
Show file tree
Hide file tree
Showing 34 changed files with 642 additions and 106 deletions.
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

219 changes: 162 additions & 57 deletions cli/args/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ use crate::args::resolve_no_prompt;
use crate::util::fs::canonicalize_path;

use super::flags_net;
use super::DENO_FUTURE;

#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct FileFlags {
Expand Down Expand Up @@ -195,7 +196,7 @@ pub struct InstallFlagsGlobal {
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum InstallKind {
#[allow(unused)]
Local,
Local(Option<AddFlags>),
Global(InstallFlagsGlobal),
}

Expand Down Expand Up @@ -913,11 +914,18 @@ impl Flags {
}
Task(_) | Check(_) | Coverage(_) | Cache(_) | Info(_) | Eval(_)
| Test(_) | Bench(_) | Repl(_) | Compile(_) | Publish(_) => {
std::env::current_dir().ok()
Some(current_dir.to_path_buf())
}
Add(_) | Bundle(_) | Completions(_) | Doc(_) | Fmt(_) | Init(_)
| Install(_) | Uninstall(_) | Jupyter(_) | Lsp | Lint(_) | Types
| Upgrade(_) | Vendor(_) => None,
| Uninstall(_) | Jupyter(_) | Lsp | Lint(_) | Types | Upgrade(_)
| Vendor(_) => None,
Install(_) => {
if *DENO_FUTURE {
Some(current_dir.to_path_buf())
} else {
None
}
}
}
}

Expand Down Expand Up @@ -1313,7 +1321,11 @@ fn clap_root() -> Command {
.subcommand(fmt_subcommand())
.subcommand(init_subcommand())
.subcommand(info_subcommand())
.subcommand(install_subcommand())
.subcommand(if *DENO_FUTURE {
future_install_subcommand()
} else {
install_subcommand()
})
.subcommand(jupyter_subcommand())
.subcommand(uninstall_subcommand())
.subcommand(lsp_subcommand())
Expand Down Expand Up @@ -2047,11 +2059,110 @@ TypeScript compiler cache: Subdirectory containing TS compiler output.",
))
}

fn install_args(cmd: Command, deno_future: bool) -> Command {
let cmd = if deno_future {
cmd.arg(
Arg::new("cmd")
.required_if_eq("global", "true")
.num_args(1..)
.value_hint(ValueHint::FilePath),
)
} else {
cmd.arg(
Arg::new("cmd")
.required(true)
.num_args(1..)
.value_hint(ValueHint::FilePath),
)
};
cmd
.arg(
Arg::new("name")
.long("name")
.short('n')
.help("Executable file name")
.required(false),
)
.arg(
Arg::new("root")
.long("root")
.help("Installation root")
.value_hint(ValueHint::DirPath),
)
.arg(
Arg::new("force")
.long("force")
.short('f')
.help("Forcefully overwrite existing installation")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("global")
.long("global")
.short('g')
.help("Install a package or script as a globally available executable")
.action(ArgAction::SetTrue),
)
.arg(env_file_arg())
}

fn future_install_subcommand() -> Command {
Command::new("install")
.visible_alias("i")
.about("Install dependencies")
.long_about(
"Installs dependencies either in the local project or globally to a bin directory.
Local installation
-------------------
If the --global flag is not set, adds dependencies to the local project's configuration
(package.json / deno.json) and installs them in the package cache. If no dependency
is specified, installs all dependencies listed in package.json.
deno install
deno install @std/bytes
deno install npm:chalk
Global installation
-------------------
If the --global flag is set, installs a script as an executable in the installation root's bin directory.
deno install --global --allow-net --allow-read jsr:@std/http/file-server
deno install -g https://examples.deno.land/color-logging.ts
To change the executable name, use -n/--name:
deno install -g --allow-net --allow-read -n serve jsr:@std/http/file-server
The executable name is inferred by default:
- Attempt to take the file stem of the URL path. The above example would
become 'file_server'.
- If the file stem is something generic like 'main', 'mod', 'index' or 'cli',
and the path has no parent, take the file name of the parent path. Otherwise
settle with the generic name.
- If the resulting name has an '@...' suffix, strip it.
To change the installation root, use --root:
deno install -g --allow-net --allow-read --root /usr/local jsr:@std/http/file-server
The installation root is determined, in order of precedence:
- --root option
- DENO_INSTALL_ROOT environment variable
- $HOME/.deno
These must be added to the path manually if required.")
.defer(|cmd| {
let cmd = runtime_args(cmd, true, true).arg(check_arg(true));
install_args(cmd, true)
})
}

fn install_subcommand() -> Command {
Command::new("install")
.about("Install script as an executable")
.long_about(
"Installs a script as an executable in the installation root's bin directory.
"Installs a script as an executable in the installation root's bin directory.
deno install --global --allow-net --allow-read jsr:@std/http/file-server
deno install -g https://examples.deno.land/color-logging.ts
Expand All @@ -2078,34 +2189,10 @@ The installation root is determined, in order of precedence:
- $HOME/.deno
These must be added to the path manually if required.")
.defer(|cmd| runtime_args(cmd, true, true).arg(Arg::new("cmd").required(true).num_args(1..).value_hint(ValueHint::FilePath))
.arg(check_arg(true))
.arg(
Arg::new("name")
.long("name")
.short('n')
.help("Executable file name")
.required(false))
.arg(
Arg::new("root")
.long("root")
.help("Installation root")
.value_hint(ValueHint::DirPath))
.arg(
Arg::new("force")
.long("force")
.short('f')
.help("Forcefully overwrite existing installation")
.action(ArgAction::SetTrue))
)
.arg(
Arg::new("global")
.long("global")
.short('g')
.help("Install a package or script as a globally available executable")
.action(ArgAction::SetTrue)
)
.arg(env_file_arg())
.defer(|cmd| {
let cmd = runtime_args(cmd, true, true).arg(check_arg(true));
install_args(cmd, false)
})
}

fn jupyter_subcommand() -> Command {
Expand Down Expand Up @@ -3555,8 +3642,17 @@ fn unsafely_ignore_certificate_errors_arg() -> Arg {
}

fn add_parse(flags: &mut Flags, matches: &mut ArgMatches) {
let packages = matches.remove_many::<String>("packages").unwrap().collect();
flags.subcommand = DenoSubcommand::Add(AddFlags { packages });
flags.subcommand = DenoSubcommand::Add(add_parse_inner(matches, None));
}

fn add_parse_inner(
matches: &mut ArgMatches,
packages: Option<clap::parser::Values<String>>,
) -> AddFlags {
let packages = packages
.unwrap_or_else(|| matches.remove_many::<String>("packages").unwrap())
.collect();
AddFlags { packages }
}

fn bench_parse(flags: &mut Flags, matches: &mut ArgMatches) {
Expand Down Expand Up @@ -3871,28 +3967,37 @@ fn info_parse(flags: &mut Flags, matches: &mut ArgMatches) {
fn install_parse(flags: &mut Flags, matches: &mut ArgMatches) {
runtime_args_parse(flags, matches, true, true);

let root = matches.remove_one::<String>("root");

let force = matches.get_flag("force");
let global = matches.get_flag("global");
let name = matches.remove_one::<String>("name");
let mut cmd_values = matches.remove_many::<String>("cmd").unwrap();

let module_url = cmd_values.next().unwrap();
let args = cmd_values.collect();

flags.subcommand = DenoSubcommand::Install(InstallFlags {
// TODO(bartlomieju): remove once `deno install` supports both local and
// global installs
global,
kind: InstallKind::Global(InstallFlagsGlobal {
name,
module_url,
args,
root,
force,
}),
});
if global || !*DENO_FUTURE {
let root = matches.remove_one::<String>("root");
let force = matches.get_flag("force");
let name = matches.remove_one::<String>("name");
let mut cmd_values =
matches.remove_many::<String>("cmd").unwrap_or_default();

let module_url = cmd_values.next().unwrap();
let args = cmd_values.collect();

flags.subcommand = DenoSubcommand::Install(InstallFlags {
// TODO(bartlomieju): remove for 2.0
global,
kind: InstallKind::Global(InstallFlagsGlobal {
name,
module_url,
args,
root,
force,
}),
});
} else {
let local_flags = matches
.remove_many("cmd")
.map(|packages| add_parse_inner(matches, Some(packages)));
flags.subcommand = DenoSubcommand::Install(InstallFlags {
global,
kind: InstallKind::Local(local_flags),
})
}
}

fn jupyter_parse(flags: &mut Flags, matches: &mut ArgMatches) {
Expand Down
3 changes: 2 additions & 1 deletion cli/factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,8 @@ impl CliFactory {
.npm_resolver
.get_or_try_init_async(async {
let fs = self.fs();
create_cli_npm_resolver(if self.options.use_byonm() {
// For `deno install` we want to force the managed resolver so it can set up `node_modules/` directory.
create_cli_npm_resolver(if self.options.use_byonm() && !matches!(self.options.sub_command(), DenoSubcommand::Install(_)) {
CliNpmResolverCreateOptions::Byonm(CliNpmResolverByonmCreateOptions {
fs: fs.clone(),
root_node_modules_dir: match self.options.node_modules_dir_path() {
Expand Down
35 changes: 35 additions & 0 deletions cli/module_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::cache::CodeCache;
use crate::cache::ModuleInfoCache;
use crate::cache::ParsedSourceCache;
use crate::emit::Emitter;
use crate::factory::CliFactory;
use crate::graph_util::graph_lock_or_exit;
use crate::graph_util::CreateGraphOptions;
use crate::graph_util::ModuleGraphBuilder;
Expand Down Expand Up @@ -64,6 +65,40 @@ use std::rc::Rc;
use std::str;
use std::sync::Arc;

pub async fn load_top_level_deps(factory: &CliFactory) -> Result<(), AnyError> {
let npm_resolver = factory.npm_resolver().await?;
if let Some(npm_resolver) = npm_resolver.as_managed() {
npm_resolver.ensure_top_level_package_json_install().await?;
npm_resolver.resolve_pending().await?;
}
// cache as many entries in the import map as we can
if let Some(import_map) = factory.maybe_import_map().await? {
let roots = import_map
.imports()
.entries()
.filter_map(|entry| {
if entry.key.ends_with('/') {
None
} else {
entry.value.cloned()
}
})
.collect();
factory
.module_load_preparer()
.await?
.prepare_module_load(
roots,
false,
factory.cli_options().ts_type_lib_window(),
deno_runtime::permissions::PermissionsContainer::allow_all(),
)
.await?;
}

Ok(())
}

pub struct ModuleLoadPreparer {
options: Arc<CliOptions>,
graph_container: Arc<ModuleGraphContainer>,
Expand Down
19 changes: 18 additions & 1 deletion cli/tools/installer.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.

use crate::args::resolve_no_prompt;
use crate::args::AddFlags;
use crate::args::CaData;
use crate::args::Flags;
use crate::args::InstallFlags;
Expand Down Expand Up @@ -253,6 +254,20 @@ pub fn uninstall(uninstall_flags: UninstallFlags) -> Result<(), AnyError> {
Ok(())
}

async fn install_local(
flags: Flags,
maybe_add_flags: Option<AddFlags>,
) -> Result<(), AnyError> {
if let Some(add_flags) = maybe_add_flags {
return super::registry::add(flags, add_flags).await;
}

let factory = CliFactory::from_flags(flags)?;
crate::module_loader::load_top_level_deps(&factory).await?;

Ok(())
}

pub async fn install_command(
flags: Flags,
install_flags: InstallFlags,
Expand All @@ -263,7 +278,9 @@ pub async fn install_command(

let install_flags_global = match install_flags.kind {
InstallKind::Global(flags) => flags,
InstallKind::Local => unreachable!(),
InstallKind::Local(maybe_add_flags) => {
return install_local(flags, maybe_add_flags).await
}
};

// ensure the module is cached
Expand Down

0 comments on commit 4e23a5b

Please sign in to comment.