summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock6
-rw-r--r--cli/args/flags.rs219
-rw-r--r--cli/factory.rs3
-rw-r--r--cli/module_loader.rs35
-rw-r--r--cli/tools/installer.rs19
-rw-r--r--cli/tools/registry/pm.rs242
-rw-r--r--tests/specs/install/future_install_global/__test__.jsonc16
-rw-r--r--tests/specs/install/future_install_global/assert.js11
-rw-r--r--tests/specs/install/future_install_global/install.out5
-rw-r--r--tests/specs/install/future_install_global/main.js3
-rw-r--r--tests/specs/install/future_install_global/package.json7
-rw-r--r--tests/specs/install/future_install_local_add_deno/__test__.jsonc24
-rw-r--r--tests/specs/install/future_install_local_add_deno/deno.json1
-rw-r--r--tests/specs/install/future_install_local_add_deno/deno.json.out5
-rw-r--r--tests/specs/install/future_install_local_add_deno/install.out4
-rw-r--r--tests/specs/install/future_install_local_add_deno/main.js2
-rw-r--r--tests/specs/install/future_install_local_add_npm/__test__.jsonc25
-rw-r--r--tests/specs/install/future_install_local_add_npm/install.out5
-rw-r--r--tests/specs/install/future_install_local_add_npm/main.js2
-rw-r--r--tests/specs/install/future_install_local_add_npm/package.json3
-rw-r--r--tests/specs/install/future_install_local_add_npm/package.json.out3
-rw-r--r--tests/specs/install/future_install_local_deno/__test__.jsonc17
-rw-r--r--tests/specs/install/future_install_local_deno/deno.json8
-rw-r--r--tests/specs/install/future_install_local_deno/install.out10
-rw-r--r--tests/specs/install/future_install_local_deno/main.js6
-rw-r--r--tests/specs/install/future_install_node_modules/__test__.jsonc17
-rw-r--r--tests/specs/install/future_install_node_modules/install.out4
-rw-r--r--tests/specs/install/future_install_node_modules/main.js3
-rw-r--r--tests/specs/install/future_install_node_modules/package.json5
-rw-r--r--tests/specs/install/no_future_install_global/__test__.jsonc13
-rw-r--r--tests/specs/install/no_future_install_global/assert.js11
-rw-r--r--tests/specs/install/no_future_install_global/install.out5
-rw-r--r--tests/specs/install/no_future_install_global/main.js3
-rw-r--r--tests/specs/install/no_future_install_global/package.json6
34 files changed, 642 insertions, 106 deletions
diff --git a/Cargo.lock b/Cargo.lock
index c5340cb07..7540e5ca5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5900,12 +5900,12 @@ dependencies = [
[[package]]
name = "socket2"
-version = "0.5.6"
+version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871"
+checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
dependencies = [
"libc",
- "windows-sys 0.52.0",
+ "windows-sys 0.48.0",
]
[[package]]
diff --git a/cli/args/flags.rs b/cli/args/flags.rs
index c03acb02a..0ef02be39 100644
--- a/cli/args/flags.rs
+++ b/cli/args/flags.rs
@@ -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 {
@@ -195,7 +196,7 @@ pub struct InstallFlagsGlobal {
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum InstallKind {
#[allow(unused)]
- Local,
+ Local(Option<AddFlags>),
Global(InstallFlagsGlobal),
}
@@ -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
+ }
+ }
}
}
@@ -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())
@@ -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
@@ -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 {
@@ -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) {
@@ -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) {
diff --git a/cli/factory.rs b/cli/factory.rs
index a9f6bf87e..074eaa1e4 100644
--- a/cli/factory.rs
+++ b/cli/factory.rs
@@ -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() {
diff --git a/cli/module_loader.rs b/cli/module_loader.rs
index a6c8d1338..7d8cb130b 100644
--- a/cli/module_loader.rs
+++ b/cli/module_loader.rs
@@ -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;
@@ -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>,
diff --git a/cli/tools/installer.rs b/cli/tools/installer.rs
index f3eba7b8a..9ff17ccc3 100644
--- a/cli/tools/installer.rs
+++ b/cli/tools/installer.rs
@@ -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;
@@ -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,
@@ -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
diff --git a/cli/tools/registry/pm.rs b/cli/tools/registry/pm.rs
index 699b476cb..e37ee3d82 100644
--- a/cli/tools/registry/pm.rs
+++ b/cli/tools/registry/pm.rs
@@ -1,19 +1,23 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
-use std::collections::HashMap;
+use std::borrow::Cow;
use std::path::PathBuf;
use std::sync::Arc;
use deno_ast::TextChange;
use deno_config::FmtOptionsConfig;
+use deno_core::anyhow::anyhow;
use deno_core::anyhow::bail;
use deno_core::anyhow::Context;
use deno_core::error::AnyError;
use deno_core::futures::FutureExt;
use deno_core::futures::StreamExt;
use deno_core::serde_json;
+use deno_core::ModuleSpecifier;
+use deno_runtime::deno_node;
use deno_semver::jsr::JsrPackageReqReference;
use deno_semver::npm::NpmPackageReqReference;
+use indexmap::IndexMap;
use jsonc_parser::ast::ObjectProp;
use jsonc_parser::ast::Value;
@@ -25,22 +29,164 @@ use crate::file_fetcher::FileFetcher;
use crate::jsr::JsrFetchResolver;
use crate::npm::NpmFetchResolver;
+enum DenoConfigFormat {
+ Json,
+ Jsonc,
+}
+
+impl DenoConfigFormat {
+ fn from_specifier(spec: &ModuleSpecifier) -> Result<Self, AnyError> {
+ let file_name = spec
+ .path_segments()
+ .ok_or_else(|| anyhow!("Empty path in deno config specifier: {spec}"))?
+ .last()
+ .unwrap();
+ match file_name {
+ "deno.json" => Ok(Self::Json),
+ "deno.jsonc" => Ok(Self::Jsonc),
+ _ => bail!("Unsupported deno config file: {file_name}"),
+ }
+ }
+}
+
+enum DenoOrPackageJson {
+ Deno(deno_config::ConfigFile, DenoConfigFormat),
+ Npm(deno_node::PackageJson, Option<FmtOptionsConfig>),
+}
+
+impl DenoOrPackageJson {
+ fn specifier(&self) -> Cow<ModuleSpecifier> {
+ match self {
+ Self::Deno(d, ..) => Cow::Borrowed(&d.specifier),
+ Self::Npm(n, ..) => Cow::Owned(n.specifier()),
+ }
+ }
+
+ /// Returns the existing imports/dependencies from the config.
+ fn existing_imports(&self) -> Result<IndexMap<String, String>, AnyError> {
+ match self {
+ DenoOrPackageJson::Deno(deno, ..) => {
+ if let Some(imports) = deno.json.imports.clone() {
+ match serde_json::from_value(imports) {
+ Ok(map) => Ok(map),
+ Err(err) => {
+ bail!("Malformed \"imports\" configuration: {err}")
+ }
+ }
+ } else {
+ Ok(Default::default())
+ }
+ }
+ DenoOrPackageJson::Npm(npm, ..) => {
+ Ok(npm.dependencies.clone().unwrap_or_default())
+ }
+ }
+ }
+
+ fn fmt_options(&self) -> FmtOptionsConfig {
+ match self {
+ DenoOrPackageJson::Deno(deno, ..) => deno
+ .to_fmt_config()
+ .ok()
+ .flatten()
+ .map(|f| f.options)
+ .unwrap_or_default(),
+ DenoOrPackageJson::Npm(_, config) => config.clone().unwrap_or_default(),
+ }
+ }
+
+ fn imports_key(&self) -> &'static str {
+ match self {
+ DenoOrPackageJson::Deno(..) => "imports",
+ DenoOrPackageJson::Npm(..) => "dependencies",
+ }
+ }
+
+ fn file_name(&self) -> &'static str {
+ match self {
+ DenoOrPackageJson::Deno(_, format) => match format {
+ DenoConfigFormat::Json => "deno.json",
+ DenoConfigFormat::Jsonc => "deno.jsonc",
+ },
+ DenoOrPackageJson::Npm(..) => "package.json",
+ }
+ }
+
+ fn is_npm(&self) -> bool {
+ matches!(self, Self::Npm(..))
+ }
+
+ /// Get the preferred config file to operate on
+ /// given the flags. If no config file is present,
+ /// creates a `deno.json` file - in this case
+ /// we also return a new `CliFactory` that knows about
+ /// the new config
+ fn from_flags(flags: Flags) -> Result<(Self, CliFactory), AnyError> {
+ let factory = CliFactory::from_flags(flags.clone())?;
+ let options = factory.cli_options().clone();
+
+ match (options.maybe_config_file(), options.maybe_package_json()) {
+ // when both are present, for now,
+ // default to deno.json
+ (Some(deno), Some(_) | None) => Ok((
+ DenoOrPackageJson::Deno(
+ deno.clone(),
+ DenoConfigFormat::from_specifier(&deno.specifier)?,
+ ),
+ factory,
+ )),
+ (None, Some(package_json)) if options.enable_future_features() => {
+ Ok((DenoOrPackageJson::Npm(package_json.clone(), None), factory))
+ }
+ (None, Some(_) | None) => {
+ std::fs::write(options.initial_cwd().join("deno.json"), "{}\n")
+ .context("Failed to create deno.json file")?;
+ log::info!("Created deno.json configuration file.");
+ let new_factory = CliFactory::from_flags(flags.clone())?;
+ let new_options = new_factory.cli_options().clone();
+ Ok((
+ DenoOrPackageJson::Deno(
+ new_options
+ .maybe_config_file()
+ .as_ref()
+ .ok_or_else(|| {
+ anyhow!("config not found, but it was just created")
+ })?
+ .clone(),
+ DenoConfigFormat::Json,
+ ),
+ new_factory,
+ ))
+ }
+ }
+ }
+}
+
+fn package_json_dependency_entry(
+ selected: SelectedPackage,
+) -> (String, String) {
+ if let Some(npm_package) = selected.package_name.strip_prefix("npm:") {
+ (npm_package.into(), selected.version_req)
+ } else if let Some(jsr_package) = selected.package_name.strip_prefix("jsr:") {
+ let jsr_package = jsr_package.strip_prefix('@').unwrap_or(jsr_package);
+ let scope_replaced = jsr_package.replace('/', "__");
+ let version_req =
+ format!("npm:@jsr/{scope_replaced}@{}", selected.version_req);
+ (selected.import_name, version_req)
+ } else {
+ (selected.package_name, selected.version_req)
+ }
+}
+
pub async fn add(flags: Flags, add_flags: AddFlags) -> Result<(), AnyError> {
- let cli_factory = CliFactory::from_flags(flags.clone())?;
- let cli_options = cli_factory.cli_options();
-
- let Some(config_file) = cli_options.maybe_config_file() else {
- tokio::fs::write(cli_options.initial_cwd().join("deno.json"), "{}\n")
- .await
- .context("Failed to create deno.json file")?;
- log::info!("Created deno.json configuration file.");
- return add(flags, add_flags).boxed_local().await;
- };
+ let (config_file, cli_factory) =
+ DenoOrPackageJson::from_flags(flags.clone())?;
- if config_file.specifier.scheme() != "file" {
+ let config_specifier = config_file.specifier();
+ if config_specifier.scheme() != "file" {
bail!("Can't add dependencies to a remote configuration file");
}
- let config_file_path = config_file.specifier.to_file_path().unwrap();
+ let config_file_path = config_specifier.to_file_path().unwrap();
let http_client = cli_factory.http_client();
@@ -49,10 +195,13 @@ pub async fn add(flags: Flags, add_flags: AddFlags) -> Result<(), AnyError> {
for package_name in add_flags.packages.iter() {
let req = if package_name.starts_with("npm:") {
- let pkg_req = NpmPackageReqReference::from_str(package_name)
- .with_context(|| {
- format!("Failed to parse package required: {}", package_name)
- })?;
+ let pkg_req = NpmPackageReqReference::from_str(&format!(
+ "npm:{}",
+ package_name.strip_prefix("npm:").unwrap_or(package_name)
+ ))
+ .with_context(|| {
+ format!("Failed to parse package required: {}", package_name)
+ })?;
AddPackageReq::Npm(pkg_req)
} else {
let pkg_req = JsrPackageReqReference::from_str(&format!(
@@ -128,16 +277,9 @@ pub async fn add(flags: Flags, add_flags: AddFlags) -> Result<(), AnyError> {
_ => bail!("Failed updating config file due to no object."),
};
- let mut existing_imports =
- if let Some(imports) = config_file.json.imports.clone() {
- match serde_json::from_value::<HashMap<String, String>>(imports) {
- Ok(i) => i,
- Err(_) => bail!("Malformed \"imports\" configuration"),
- }
- } else {
- HashMap::default()
- };
+ let mut existing_imports = config_file.existing_imports()?;
+ let is_npm = config_file.is_npm();
for selected_package in selected_packages {
log::info!(
"Add {} - {}@{}",
@@ -145,13 +287,19 @@ pub async fn add(flags: Flags, add_flags: AddFlags) -> Result<(), AnyError> {
selected_package.package_name,
selected_package.version_req
);
- existing_imports.insert(
- selected_package.import_name,
- format!(
- "{}@{}",
- selected_package.package_name, selected_package.version_req
- ),
- );
+
+ if is_npm {
+ let (name, version) = package_json_dependency_entry(selected_package);
+ existing_imports.insert(name, version)
+ } else {
+ existing_imports.insert(
+ selected_package.import_name,
+ format!(
+ "{}@{}",
+ selected_package.package_name, selected_package.version_req
+ ),
+ )
+ };
}
let mut import_list: Vec<(String, String)> =
existing_imports.into_iter().collect();
@@ -159,25 +307,29 @@ pub async fn add(flags: Flags, add_flags: AddFlags) -> Result<(), AnyError> {
import_list.sort_by(|(k1, _), (k2, _)| k1.cmp(k2));
let generated_imports = generate_imports(import_list);
- let fmt_config_options = config_file
- .to_fmt_config()
- .ok()
- .flatten()
- .map(|config| config.options)
- .unwrap_or_default();
+ let fmt_config_options = config_file.fmt_options();
let new_text = update_config_file_content(
obj,
&config_file_contents,
generated_imports,
fmt_config_options,
+ config_file.imports_key(),
+ config_file.file_name(),
);
tokio::fs::write(&config_file_path, new_text)
.await
.context("Failed to update configuration file")?;
- // TODO(bartlomieju): we should now cache the imports from the config file.
+ // TODO(bartlomieju): we should now cache the imports from the deno.json.
+
+ // make a new CliFactory to pick up the updated config file
+ let cli_factory = CliFactory::from_flags(flags)?;
+ // cache deps
+ if cli_factory.cli_options().enable_future_features() {
+ crate::module_loader::load_top_level_deps(&cli_factory).await?;
+ }
Ok(())
}
@@ -259,10 +411,12 @@ fn update_config_file_content(
config_file_contents: &str,
generated_imports: String,
fmt_options: FmtOptionsConfig,
+ imports_key: &str,
+ file_name: &str,
) -> String {
let mut text_changes = vec![];
- match obj.get("imports") {
+ match obj.get(imports_key) {
Some(ObjectProp {
value: Value::Object(lit),
..
@@ -282,10 +436,10 @@ fn update_config_file_content(
// "<package_name>": "<registry>:<package_name>@<semver>"
// }
// }
- new_text: format!("\"imports\": {{\n {} }}", generated_imports),
+ new_text: format!("\"{imports_key}\": {{\n {generated_imports} }}"),
})
}
- // we verified the shape of `imports` above
+ // we verified the shape of `imports`/`dependencies` above
Some(_) => unreachable!(),
}
@@ -293,7 +447,7 @@ fn update_config_file_content(
deno_ast::apply_text_changes(config_file_contents, text_changes);
crate::tools::fmt::format_json(
- &PathBuf::from("deno.json"),
+ &PathBuf::from(file_name),
&new_text,
&fmt_options,
)
diff --git a/tests/specs/install/future_install_global/__test__.jsonc b/tests/specs/install/future_install_global/__test__.jsonc
new file mode 100644
index 000000000..be6fcab97
--- /dev/null
+++ b/tests/specs/install/future_install_global/__test__.jsonc
@@ -0,0 +1,16 @@
+{
+ "tempDir": true,
+ "envs": {
+ "DENO_FUTURE": "1"
+ },
+ "steps": [
+ {
+ "args": "install --global --root ./bins --name deno-test-bin ./main.js",
+ "output": "install.out"
+ },
+ {
+ "args": "run -A assert.js",
+ "output": ""
+ }
+ ]
+}
diff --git a/tests/specs/install/future_install_global/assert.js b/tests/specs/install/future_install_global/assert.js
new file mode 100644
index 000000000..c7a6a0680
--- /dev/null
+++ b/tests/specs/install/future_install_global/assert.js
@@ -0,0 +1,11 @@
+const dirs = Deno.readDir("./bins/bin");
+
+let found = false;
+for await (const entry of dirs) {
+ if (entry.name.includes("deno-test-bin")) {
+ found = true;
+ }
+}
+if (!found) {
+ throw new Error("Failed to find test bin");
+}
diff --git a/tests/specs/install/future_install_global/install.out b/tests/specs/install/future_install_global/install.out
new file mode 100644
index 000000000..adb8b4598
--- /dev/null
+++ b/tests/specs/install/future_install_global/install.out
@@ -0,0 +1,5 @@
+Download http://localhost:4260/@denotest/esm-basic
+Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz
+Initialize @denotest/esm-basic@1.0.0
+✅ Successfully installed deno-test-bin[WILDCARD]
+[WILDCARD]
diff --git a/tests/specs/install/future_install_global/main.js b/tests/specs/install/future_install_global/main.js
new file mode 100644
index 000000000..2ba55540b
--- /dev/null
+++ b/tests/specs/install/future_install_global/main.js
@@ -0,0 +1,3 @@
+import { setValue } from "@denotest/esm-basic";
+
+setValue(5);
diff --git a/tests/specs/install/future_install_global/package.json b/tests/specs/install/future_install_global/package.json
new file mode 100644
index 000000000..f3b6cb7be
--- /dev/null
+++ b/tests/specs/install/future_install_global/package.json
@@ -0,0 +1,7 @@
+{
+ "name": "deno-test-bin",
+ "dependencies": {
+ "@denotest/esm-basic": "*"
+ },
+ "type": "module"
+}
diff --git a/tests/specs/install/future_install_local_add_deno/__test__.jsonc b/tests/specs/install/future_install_local_add_deno/__test__.jsonc
new file mode 100644
index 000000000..eaafafbdd
--- /dev/null
+++ b/tests/specs/install/future_install_local_add_deno/__test__.jsonc
@@ -0,0 +1,24 @@
+{
+ "tempDir": true,
+ "envs": {
+ "DENO_FUTURE": "1"
+ },
+ "steps": [
+ {
+ "args": "install npm:@denotest/esm-basic",
+ "output": "install.out"
+ },
+ {
+ // make sure the dep got cached
+ "args": "run --cached-only main.js",
+ "output": ""
+ },
+ {
+ "args": [
+ "eval",
+ "console.log(Deno.readTextFileSync('deno.json').trim())"
+ ],
+ "output": "deno.json.out"
+ }
+ ]
+}
diff --git a/tests/specs/install/future_install_local_add_deno/deno.json b/tests/specs/install/future_install_local_add_deno/deno.json
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/tests/specs/install/future_install_local_add_deno/deno.json
@@ -0,0 +1 @@
+{}
diff --git a/tests/specs/install/future_install_local_add_deno/deno.json.out b/tests/specs/install/future_install_local_add_deno/deno.json.out
new file mode 100644
index 000000000..0172071c3
--- /dev/null
+++ b/tests/specs/install/future_install_local_add_deno/deno.json.out
@@ -0,0 +1,5 @@
+{
+ "imports": {
+ "@denotest/esm-basic": "npm:@denotest/esm-basic@^1.0.0"
+ }
+}
diff --git a/tests/specs/install/future_install_local_add_deno/install.out b/tests/specs/install/future_install_local_add_deno/install.out
new file mode 100644
index 000000000..df58284fc
--- /dev/null
+++ b/tests/specs/install/future_install_local_add_deno/install.out
@@ -0,0 +1,4 @@
+⚠️ `deno install` behavior will change in Deno 2. To preserve the current behavior use the `-g` or `--global` flag.
+Add @denotest/esm-basic - npm:@denotest/esm-basic@^1.0.0
+Download http://localhost:4260/@denotest/esm-basic
+Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz
diff --git a/tests/specs/install/future_install_local_add_deno/main.js b/tests/specs/install/future_install_local_add_deno/main.js
new file mode 100644
index 000000000..c29b48b4e
--- /dev/null
+++ b/tests/specs/install/future_install_local_add_deno/main.js
@@ -0,0 +1,2 @@
+import { setValue } from "@denotest/esm-basic";
+setValue(5);
diff --git a/tests/specs/install/future_install_local_add_npm/__test__.jsonc b/tests/specs/install/future_install_local_add_npm/__test__.jsonc
new file mode 100644
index 000000000..19ded2ab5
--- /dev/null
+++ b/tests/specs/install/future_install_local_add_npm/__test__.jsonc
@@ -0,0 +1,25 @@
+{
+ "tempDir": true,
+ "envs": {
+ "DENO_FUTURE": "1"
+ },
+ "steps": [
+ {
+ "args": "install npm:@denotest/esm-basic",
+ "output": "install.out"
+ },
+ {
+ // make sure the dep got cached
+ "args": "run --cached-only main.js",
+ "exitCode": 0,
+ "output": ""
+ },
+ {
+ "args": [
+ "eval",
+ "console.log(Deno.readTextFileSync('package.json').trim())"
+ ],
+ "output": "package.json.out"
+ }
+ ]
+}
diff --git a/tests/specs/install/future_install_local_add_npm/install.out b/tests/specs/install/future_install_local_add_npm/install.out
new file mode 100644
index 000000000..d9c23abf0
--- /dev/null
+++ b/tests/specs/install/future_install_local_add_npm/install.out
@@ -0,0 +1,5 @@
+⚠️ `deno install` behavior will change in Deno 2. To preserve the current behavior use the `-g` or `--global` flag.
+Add @denotest/esm-basic - npm:@denotest/esm-basic@^1.0.0
+Download http://localhost:4260/@denotest/esm-basic
+Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz
+Initialize @denotest/esm-basic@1.0.0
diff --git a/tests/specs/install/future_install_local_add_npm/main.js b/tests/specs/install/future_install_local_add_npm/main.js
new file mode 100644
index 000000000..c29b48b4e
--- /dev/null
+++ b/tests/specs/install/future_install_local_add_npm/main.js
@@ -0,0 +1,2 @@
+import { setValue } from "@denotest/esm-basic";
+setValue(5);
diff --git a/tests/specs/install/future_install_local_add_npm/package.json b/tests/specs/install/future_install_local_add_npm/package.json
new file mode 100644
index 000000000..18a1e415e
--- /dev/null
+++ b/tests/specs/install/future_install_local_add_npm/package.json
@@ -0,0 +1,3 @@
+{
+ "dependencies": {}
+}
diff --git a/tests/specs/install/future_install_local_add_npm/package.json.out b/tests/specs/install/future_install_local_add_npm/package.json.out
new file mode 100644
index 000000000..ad8518e79
--- /dev/null
+++ b/tests/specs/install/future_install_local_add_npm/package.json.out
@@ -0,0 +1,3 @@
+{
+ "dependencies": { "@denotest/esm-basic": "^1.0.0" }
+}
diff --git a/tests/specs/install/future_install_local_deno/__test__.jsonc b/tests/specs/install/future_install_local_deno/__test__.jsonc
new file mode 100644
index 000000000..eb780d962
--- /dev/null
+++ b/tests/specs/install/future_install_local_deno/__test__.jsonc
@@ -0,0 +1,17 @@
+{
+ "tempDir": true,
+ "envs": {
+ "DENO_FUTURE": "1"
+ },
+ "steps": [
+ {
+ "args": "install",
+ "output": "install.out"
+ },
+ {
+ // ensure deps are actually cached
+ "args": "run --cached-only main.js",
+ "output": ""
+ }
+ ]
+}
diff --git a/tests/specs/install/future_install_local_deno/deno.json b/tests/specs/install/future_install_local_deno/deno.json
new file mode 100644
index 000000000..dbcf1c220
--- /dev/null
+++ b/tests/specs/install/future_install_local_deno/deno.json
@@ -0,0 +1,8 @@
+{
+ "imports": {
+ "@std/fs/": "https://deno.land/std@0.224.0/fs/",
+ "@denotest/esm-basic": "npm:@denotest/esm-basic@^1.0.0",
+ "@denotest/add": "jsr:@denotest/add",
+ "test-http": "http://localhost:4545/v1/extensionless"
+ }
+}
diff --git a/tests/specs/install/future_install_local_deno/install.out b/tests/specs/install/future_install_local_deno/install.out
new file mode 100644
index 000000000..efb77d8f2
--- /dev/null
+++ b/tests/specs/install/future_install_local_deno/install.out
@@ -0,0 +1,10 @@
+⚠️ `deno install` behavior will change in Deno 2. To preserve the current behavior use the `-g` or `--global` flag.
+Download http://localhost:4545/v1/extensionless
+Download http://localhost:4545/subdir/mod1.ts
+Download http://localhost:4545/subdir/subdir2/mod2.ts
+Download http://localhost:4545/subdir/print_hello.ts
+Download http://127.0.0.1:4250/@denotest/add/meta.json
+Download http://127.0.0.1:4250/@denotest/add/1.0.0_meta.json
+Download http://127.0.0.1:4250/@denotest/add/1.0.0/mod.ts
+Download http://localhost:4260/@denotest/esm-basic
+Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz
diff --git a/tests/specs/install/future_install_local_deno/main.js b/tests/specs/install/future_install_local_deno/main.js
new file mode 100644
index 000000000..5c09f1f20
--- /dev/null
+++ b/tests/specs/install/future_install_local_deno/main.js
@@ -0,0 +1,6 @@
+import { setValue } from "@denotest/esm-basic";
+import { add } from "@denotest/add";
+import { returnsHi } from "test-http";
+setValue(5);
+returnsHi();
+add(2, 2);
diff --git a/tests/specs/install/future_install_node_modules/__test__.jsonc b/tests/specs/install/future_install_node_modules/__test__.jsonc
new file mode 100644
index 000000000..eb780d962
--- /dev/null
+++ b/tests/specs/install/future_install_node_modules/__test__.jsonc
@@ -0,0 +1,17 @@
+{
+ "tempDir": true,
+ "envs": {
+ "DENO_FUTURE": "1"
+ },
+ "steps": [
+ {
+ "args": "install",
+ "output": "install.out"
+ },
+ {
+ // ensure deps are actually cached
+ "args": "run --cached-only main.js",
+ "output": ""
+ }
+ ]
+}
diff --git a/tests/specs/install/future_install_node_modules/install.out b/tests/specs/install/future_install_node_modules/install.out
new file mode 100644
index 000000000..22574688a
--- /dev/null
+++ b/tests/specs/install/future_install_node_modules/install.out
@@ -0,0 +1,4 @@
+⚠️ `deno install` behavior will change in Deno 2. To preserve the current behavior use the `-g` or `--global` flag.
+Download http://localhost:4260/@denotest/esm-basic
+Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz
+Initialize @denotest/esm-basic@1.0.0
diff --git a/tests/specs/install/future_install_node_modules/main.js b/tests/specs/install/future_install_node_modules/main.js
new file mode 100644
index 000000000..2ba55540b
--- /dev/null
+++ b/tests/specs/install/future_install_node_modules/main.js
@@ -0,0 +1,3 @@
+import { setValue } from "@denotest/esm-basic";
+
+setValue(5);
diff --git a/tests/specs/install/future_install_node_modules/package.json b/tests/specs/install/future_install_node_modules/package.json
new file mode 100644
index 000000000..54ca824d6
--- /dev/null
+++ b/tests/specs/install/future_install_node_modules/package.json
@@ -0,0 +1,5 @@
+{
+ "dependencies": {
+ "@denotest/esm-basic": "*"
+ }
+}
diff --git a/tests/specs/install/no_future_install_global/__test__.jsonc b/tests/specs/install/no_future_install_global/__test__.jsonc
new file mode 100644
index 000000000..ca351ee0d
--- /dev/null
+++ b/tests/specs/install/no_future_install_global/__test__.jsonc
@@ -0,0 +1,13 @@
+{
+ "tempDir": true,
+ "steps": [
+ {
+ "args": "install --root ./bins --name deno-test-bin ./main.js",
+ "output": "install.out"
+ },
+ {
+ "args": "run -A ./assert.js",
+ "output": ""
+ }
+ ]
+}
diff --git a/tests/specs/install/no_future_install_global/assert.js b/tests/specs/install/no_future_install_global/assert.js
new file mode 100644
index 000000000..c7a6a0680
--- /dev/null
+++ b/tests/specs/install/no_future_install_global/assert.js
@@ -0,0 +1,11 @@
+const dirs = Deno.readDir("./bins/bin");
+
+let found = false;
+for await (const entry of dirs) {
+ if (entry.name.includes("deno-test-bin")) {
+ found = true;
+ }
+}
+if (!found) {
+ throw new Error("Failed to find test bin");
+}
diff --git a/tests/specs/install/no_future_install_global/install.out b/tests/specs/install/no_future_install_global/install.out
new file mode 100644
index 000000000..f3a394c6f
--- /dev/null
+++ b/tests/specs/install/no_future_install_global/install.out
@@ -0,0 +1,5 @@
+⚠️ `deno install` behavior will change in Deno 2. To preserve the current behavior use the `-g` or `--global` flag.
+Download http://localhost:4260/@denotest/esm-basic
+Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz
+✅ Successfully installed deno-test-bin[WILDCARD]
+[WILDCARD]
diff --git a/tests/specs/install/no_future_install_global/main.js b/tests/specs/install/no_future_install_global/main.js
new file mode 100644
index 000000000..6268d7136
--- /dev/null
+++ b/tests/specs/install/no_future_install_global/main.js
@@ -0,0 +1,3 @@
+import { setValue } from "npm:@denotest/esm-basic";
+
+setValue(5);
diff --git a/tests/specs/install/no_future_install_global/package.json b/tests/specs/install/no_future_install_global/package.json
new file mode 100644
index 000000000..9f465ad50
--- /dev/null
+++ b/tests/specs/install/no_future_install_global/package.json
@@ -0,0 +1,6 @@
+{
+ "name": "deno-test-bin",
+ "dependencies": {
+ "@denotest/esm-basic": "*"
+ }
+}