From 3eda179220370e9c9093427fb694eed746548008 Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Mon, 14 Oct 2024 13:29:50 +0100 Subject: feat(cli): improve deno info output for npm packages (#25906) --- cli/tools/info.rs | 65 +++++++++++++++++++++++-------------------------------- 1 file changed, 27 insertions(+), 38 deletions(-) (limited to 'cli/tools') diff --git a/cli/tools/info.rs b/cli/tools/info.rs index 1c83abe3b..7f8d68ae1 100644 --- a/cli/tools/info.rs +++ b/cli/tools/info.rs @@ -17,6 +17,7 @@ use deno_graph::Module; use deno_graph::ModuleError; use deno_graph::ModuleGraph; use deno_graph::Resolution; +use deno_npm::npm_rc::ResolvedNpmRc; use deno_npm::resolution::NpmResolutionSnapshot; use deno_npm::NpmPackageId; use deno_npm::NpmResolutionPackage; @@ -47,6 +48,7 @@ pub async fn info( let module_graph_creator = factory.module_graph_creator().await?; let npm_resolver = factory.npm_resolver().await?; let maybe_lockfile = cli_options.maybe_lockfile(); + let npmrc = cli_options.npmrc(); let resolver = factory.workspace_resolver().await?; let maybe_import_specifier = @@ -88,7 +90,8 @@ pub async fn info( JSON_SCHEMA_VERSION.into(), ); } - add_npm_packages_to_json(&mut json_graph, npm_resolver.as_ref()); + + add_npm_packages_to_json(&mut json_graph, npm_resolver.as_ref(), npmrc); display::write_json_to_stdout(&json_graph)?; } else { let mut output = String::new(); @@ -185,6 +188,7 @@ fn print_cache_info( fn add_npm_packages_to_json( json: &mut serde_json::Value, npm_resolver: &dyn CliNpmResolver, + npmrc: &ResolvedNpmRc, ) { let Some(npm_resolver) = npm_resolver.as_managed() else { return; // does not include byonm to deno info's output @@ -195,45 +199,28 @@ fn add_npm_packages_to_json( let json = json.as_object_mut().unwrap(); let modules = json.get_mut("modules").and_then(|m| m.as_array_mut()); if let Some(modules) = modules { - if modules.len() == 1 - && modules[0].get("kind").and_then(|k| k.as_str()) == Some("npm") - { - // If there is only one module and it's "external", then that means - // someone provided an npm specifier as a cli argument. In this case, - // we want to show which npm package the cli argument resolved to. - let module = &mut modules[0]; - let maybe_package = module - .get("specifier") - .and_then(|k| k.as_str()) - .and_then(|specifier| NpmPackageNvReference::from_str(specifier).ok()) - .and_then(|package_ref| { - snapshot - .resolve_package_from_deno_module(package_ref.nv()) - .ok() - }); - if let Some(pkg) = maybe_package { - if let Some(module) = module.as_object_mut() { - module - .insert("npmPackage".to_string(), pkg.id.as_serialized().into()); - } - } - } else { - // Filter out npm package references from the modules and instead - // have them only listed as dependencies. This is done because various - // npm specifiers modules in the graph are really just unresolved - // references. So there could be listed multiple npm specifiers - // that would resolve to a single npm package. - for i in (0..modules.len()).rev() { - if matches!( - modules[i].get("kind").and_then(|k| k.as_str()), - Some("npm") | Some("external") - ) { - modules.remove(i); + for module in modules.iter_mut() { + if matches!(module.get("kind").and_then(|k| k.as_str()), Some("npm")) { + // If there is only one module and it's "external", then that means + // someone provided an npm specifier as a cli argument. In this case, + // we want to show which npm package the cli argument resolved to. + let maybe_package = module + .get("specifier") + .and_then(|k| k.as_str()) + .and_then(|specifier| NpmPackageNvReference::from_str(specifier).ok()) + .and_then(|package_ref| { + snapshot + .resolve_package_from_deno_module(package_ref.nv()) + .ok() + }); + if let Some(pkg) = maybe_package { + if let Some(module) = module.as_object_mut() { + module + .insert("npmPackage".to_string(), pkg.id.as_serialized().into()); + } } } - } - for module in modules.iter_mut() { let dependencies = module .get_mut("dependencies") .and_then(|d| d.as_array_mut()); @@ -265,7 +252,7 @@ fn add_npm_packages_to_json( let mut json_packages = serde_json::Map::with_capacity(sorted_packages.len()); for pkg in sorted_packages { let mut kv = serde_json::Map::new(); - kv.insert("name".to_string(), pkg.id.nv.name.to_string().into()); + kv.insert("name".to_string(), pkg.id.nv.name.clone().into()); kv.insert("version".to_string(), pkg.id.nv.version.to_string().into()); let mut deps = pkg.dependencies.values().collect::>(); deps.sort(); @@ -274,6 +261,8 @@ fn add_npm_packages_to_json( .map(|id| serde_json::Value::String(id.as_serialized())) .collect::>(); kv.insert("dependencies".to_string(), deps.into()); + let registry_url = npmrc.get_registry_url(&pkg.id.nv.name); + kv.insert("registryUrl".to_string(), registry_url.to_string().into()); json_packages.insert(pkg.id.as_serialized(), kv.into()); } -- cgit v1.2.3 From c5449d71da2d623e866d733b6db180a6f94ff7c6 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Mon, 14 Oct 2024 15:35:52 -0400 Subject: fix(install): support installing npm package with alias (#26246) Just tried this out today and it wasn't properly implemented in https://github.com/denoland/deno/pull/24156 --- cli/tools/registry/pm.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'cli/tools') diff --git a/cli/tools/registry/pm.rs b/cli/tools/registry/pm.rs index 5dc042620..3276acfbf 100644 --- a/cli/tools/registry/pm.rs +++ b/cli/tools/registry/pm.rs @@ -363,7 +363,14 @@ 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) + if selected.import_name == npm_package { + (npm_package.into(), selected.version_req) + } else { + ( + selected.import_name, + format!("npm:{}@{}", npm_package, 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('/', "__"); @@ -741,6 +748,9 @@ fn generate_imports(mut packages_to_version: Vec<(String, String)>) -> String { let mut contents = vec![]; let len = packages_to_version.len(); for (index, (package, version)) in packages_to_version.iter().enumerate() { + if index == 0 { + contents.push(String::new()); // force a newline at the start + } // TODO(bartlomieju): fix it, once we start support specifying version on the cli contents.push(format!("\"{}\": \"{}\"", package, version)); if index != len - 1 { -- cgit v1.2.3 From 1a0cb5b5312941521ab021cfe9eaed498f35900b Mon Sep 17 00:00:00 2001 From: David Sherret Date: Mon, 14 Oct 2024 20:48:39 -0400 Subject: feat(unstable): `--unstable-detect-cjs` for respecting explicit `"type": "commonjs"` (#26149) When using the `--unstable-detect-cjs` flag or adding `"unstable": ["detect-cjs"]` to a deno.json, it will make a JS file CJS if the closest package.json contains `"type": "commonjs"` and the file is not an ESM module (no TLA, no `import.meta`, no `import`/`export`). --- cli/tools/compile.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'cli/tools') diff --git a/cli/tools/compile.rs b/cli/tools/compile.rs index c1f98bc08..3cc4414fc 100644 --- a/cli/tools/compile.rs +++ b/cli/tools/compile.rs @@ -54,6 +54,16 @@ pub async fn compile( ); } + if cli_options.unstable_detect_cjs() { + log::warn!( + concat!( + "{} --unstable-detect-cjs is not properly supported in deno compile. ", + "The compiled executable may encounter runtime errors.", + ), + crate::colors::yellow("Warning"), + ); + } + let output_path = resolve_compile_executable_output_path( http_client, &compile_flags, -- cgit v1.2.3 From 797405fc61b2d155941506fb53d498076e121017 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker <17734409+nathanwhit@users.noreply.github.com> Date: Tue, 15 Oct 2024 09:38:42 -0700 Subject: fix(add): create deno.json when running `deno add jsr:` (#26275) Fixes https://github.com/denoland/deno/issues/26119. Originally I wanted to put them in package.json if there's no deno.json, but on second thought it makes more sense to just create a deno.json --- cli/tools/registry/pm.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'cli/tools') diff --git a/cli/tools/registry/pm.rs b/cli/tools/registry/pm.rs index 3276acfbf..ff900d113 100644 --- a/cli/tools/registry/pm.rs +++ b/cli/tools/registry/pm.rs @@ -400,14 +400,17 @@ impl std::fmt::Display for AddCommandName { fn load_configs( flags: &Arc, + has_jsr_specifiers: impl FnOnce() -> bool, ) -> Result<(CliFactory, Option, Option), AnyError> { let cli_factory = CliFactory::from_flags(flags.clone()); let options = cli_factory.cli_options()?; let npm_config = NpmConfig::from_options(options)?; let (cli_factory, deno_config) = match DenoConfig::from_options(options)? { Some(config) => (cli_factory, Some(config)), - None if npm_config.is_some() => (cli_factory, None), - None => { + None if npm_config.is_some() && !has_jsr_specifiers() => { + (cli_factory, None) + } + _ => { let factory = create_deno_json(flags, options)?; let options = factory.cli_options()?.clone(); ( @@ -427,7 +430,9 @@ pub async fn add( add_flags: AddFlags, cmd_name: AddCommandName, ) -> Result<(), AnyError> { - let (cli_factory, npm_config, deno_config) = load_configs(&flags)?; + let (cli_factory, npm_config, deno_config) = load_configs(&flags, || { + add_flags.packages.iter().any(|s| s.starts_with("jsr:")) + })?; let mut npm_config = ConfigUpdater::maybe_new(npm_config).await?; let mut deno_config = ConfigUpdater::maybe_new(deno_config).await?; @@ -764,7 +769,7 @@ pub async fn remove( flags: Arc, remove_flags: RemoveFlags, ) -> Result<(), AnyError> { - let (_, npm_config, deno_config) = load_configs(&flags)?; + let (_, npm_config, deno_config) = load_configs(&flags, || false)?; let mut configs = [ ConfigUpdater::maybe_new(npm_config).await?, -- cgit v1.2.3 From 7c3c13cecf48219cdcb90dc0b5019686cdd88626 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker <17734409+nathanwhit@users.noreply.github.com> Date: Tue, 15 Oct 2024 16:46:42 -0700 Subject: fix(install): retry downloads of registry info / tarballs (#26278) Fixes #26085. Adds a basic retry utility with some defaults, starts off with a 100ms wait, then 250ms, then 500ms I've applied the retry in the http client, reusing an existing function, so this also applies to retrying downloads of deno binaries in `upgrade` and `compile`. I can make a separate function that doesn't retry so this doesn't affect `upgrade` and `compile`, but it seemed desirable to have retries there too, so I left it in. --- cli/tools/upgrade.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'cli/tools') diff --git a/cli/tools/upgrade.rs b/cli/tools/upgrade.rs index 7f21e6649..b1b09d1a6 100644 --- a/cli/tools/upgrade.rs +++ b/cli/tools/upgrade.rs @@ -913,7 +913,7 @@ async fn download_package( // text above which will stay alive after the progress bars are complete let progress = progress_bar.update(""); let maybe_bytes = client - .download_with_progress(download_url.clone(), None, &progress) + .download_with_progress_and_retries(download_url.clone(), None, &progress) .await .with_context(|| format!("Failed downloading {download_url}. The version you requested may not have been built for the current architecture."))?; Ok(maybe_bytes) -- cgit v1.2.3 From 2929d583d2f690b5a3d600296363a8ecd90440eb Mon Sep 17 00:00:00 2001 From: Satya Rohith Date: Wed, 16 Oct 2024 14:50:41 +0530 Subject: fix(cli): consolidate pkg parser for install & remove (#26298) Closes https://github.com/denoland/deno/issues/26283 --- cli/tools/registry/pm.rs | 84 ++++++++++++++++++++++++++++++------------------ 1 file changed, 52 insertions(+), 32 deletions(-) (limited to 'cli/tools') diff --git a/cli/tools/registry/pm.rs b/cli/tools/registry/pm.rs index ff900d113..f716dd2ca 100644 --- a/cli/tools/registry/pm.rs +++ b/cli/tools/registry/pm.rs @@ -470,7 +470,7 @@ pub async fn add( let mut package_reqs = Vec::with_capacity(add_flags.packages.len()); for entry_text in add_flags.packages.iter() { - let req = AddPackageReq::parse(entry_text).with_context(|| { + let req = AddRmPackageReq::parse(entry_text).with_context(|| { format!("Failed to parse package required: {}", entry_text) })?; @@ -596,10 +596,10 @@ enum PackageAndVersion { async fn find_package_and_select_version_for_req( jsr_resolver: Arc, npm_resolver: Arc, - add_package_req: AddPackageReq, + add_package_req: AddRmPackageReq, ) -> Result { match add_package_req.value { - AddPackageReqValue::Jsr(req) => { + AddRmPackageReqValue::Jsr(req) => { let jsr_prefixed_name = format!("jsr:{}", &req.name); let Some(nv) = jsr_resolver.req_to_nv(&req).await else { if npm_resolver.req_to_nv(&req).await.is_some() { @@ -628,7 +628,7 @@ async fn find_package_and_select_version_for_req( selected_version: nv.version.to_string(), })) } - AddPackageReqValue::Npm(req) => { + AddRmPackageReqValue::Npm(req) => { let npm_prefixed_name = format!("npm:{}", &req.name); let Some(nv) = npm_resolver.req_to_nv(&req).await else { return Ok(PackageAndVersion::NotFound { @@ -653,18 +653,18 @@ async fn find_package_and_select_version_for_req( } #[derive(Debug, PartialEq, Eq)] -enum AddPackageReqValue { +enum AddRmPackageReqValue { Jsr(PackageReq), Npm(PackageReq), } #[derive(Debug, PartialEq, Eq)] -struct AddPackageReq { +struct AddRmPackageReq { alias: String, - value: AddPackageReqValue, + value: AddRmPackageReqValue, } -impl AddPackageReq { +impl AddRmPackageReq { pub fn parse(entry_text: &str) -> Result, AnyError> { enum Prefix { Jsr, @@ -719,9 +719,9 @@ impl AddPackageReq { let req_ref = JsrPackageReqReference::from_str(&format!("jsr:{}", entry_text))?; let package_req = req_ref.into_inner().req; - Ok(Ok(AddPackageReq { + Ok(Ok(AddRmPackageReq { alias: maybe_alias.unwrap_or_else(|| package_req.name.to_string()), - value: AddPackageReqValue::Jsr(package_req), + value: AddRmPackageReqValue::Jsr(package_req), })) } Prefix::Npm => { @@ -739,9 +739,9 @@ impl AddPackageReq { deno_semver::RangeSetOrTag::Tag("latest".into()), ); } - Ok(Ok(AddPackageReq { + Ok(Ok(AddRmPackageReq { alias: maybe_alias.unwrap_or_else(|| package_req.name.to_string()), - value: AddPackageReqValue::Npm(package_req), + value: AddRmPackageReqValue::Npm(package_req), })) } } @@ -779,12 +779,28 @@ pub async fn remove( let mut removed_packages = vec![]; for package in &remove_flags.packages { - let mut removed = false; + let req = AddRmPackageReq::parse(package).with_context(|| { + format!("Failed to parse package required: {}", package) + })?; + let mut parsed_pkg_name = None; for config in configs.iter_mut().flatten() { - removed |= config.remove(package); + match &req { + Ok(rm_pkg) => { + if config.remove(&rm_pkg.alias) && parsed_pkg_name.is_none() { + parsed_pkg_name = Some(rm_pkg.alias.clone()); + } + } + Err(pkg) => { + // An alias or a package name without registry/version + // constraints. Try to remove the package anyway. + if config.remove(&pkg.name) && parsed_pkg_name.is_none() { + parsed_pkg_name = Some(pkg.name.clone()); + } + } + } } - if removed { - removed_packages.push(package.clone()); + if let Some(pkg) = parsed_pkg_name { + removed_packages.push(pkg); } } @@ -913,48 +929,52 @@ mod test { #[test] fn test_parse_add_package_req() { assert_eq!( - AddPackageReq::parse("jsr:foo").unwrap().unwrap(), - AddPackageReq { + AddRmPackageReq::parse("jsr:foo").unwrap().unwrap(), + AddRmPackageReq { alias: "foo".to_string(), - value: AddPackageReqValue::Jsr(PackageReq::from_str("foo").unwrap()) + value: AddRmPackageReqValue::Jsr(PackageReq::from_str("foo").unwrap()) } ); assert_eq!( - AddPackageReq::parse("alias@jsr:foo").unwrap().unwrap(), - AddPackageReq { + AddRmPackageReq::parse("alias@jsr:foo").unwrap().unwrap(), + AddRmPackageReq { alias: "alias".to_string(), - value: AddPackageReqValue::Jsr(PackageReq::from_str("foo").unwrap()) + value: AddRmPackageReqValue::Jsr(PackageReq::from_str("foo").unwrap()) } ); assert_eq!( - AddPackageReq::parse("@alias/pkg@npm:foo").unwrap().unwrap(), - AddPackageReq { + AddRmPackageReq::parse("@alias/pkg@npm:foo") + .unwrap() + .unwrap(), + AddRmPackageReq { alias: "@alias/pkg".to_string(), - value: AddPackageReqValue::Npm( + value: AddRmPackageReqValue::Npm( PackageReq::from_str("foo@latest").unwrap() ) } ); assert_eq!( - AddPackageReq::parse("@alias/pkg@jsr:foo").unwrap().unwrap(), - AddPackageReq { + AddRmPackageReq::parse("@alias/pkg@jsr:foo") + .unwrap() + .unwrap(), + AddRmPackageReq { alias: "@alias/pkg".to_string(), - value: AddPackageReqValue::Jsr(PackageReq::from_str("foo").unwrap()) + value: AddRmPackageReqValue::Jsr(PackageReq::from_str("foo").unwrap()) } ); assert_eq!( - AddPackageReq::parse("alias@jsr:foo@^1.5.0") + AddRmPackageReq::parse("alias@jsr:foo@^1.5.0") .unwrap() .unwrap(), - AddPackageReq { + AddRmPackageReq { alias: "alias".to_string(), - value: AddPackageReqValue::Jsr( + value: AddRmPackageReqValue::Jsr( PackageReq::from_str("foo@^1.5.0").unwrap() ) } ); assert_eq!( - AddPackageReq::parse("@scope/pkg@tag") + AddRmPackageReq::parse("@scope/pkg@tag") .unwrap() .unwrap_err() .to_string(), -- cgit v1.2.3 From 19cb0d949a147097fbc03a95a07bd6bf8f7b9571 Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Wed, 16 Oct 2024 05:45:21 -0700 Subject: fix(jupyter): copy kernels icons to the kernel directory (#26084) --- cli/tools/jupyter/install.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'cli/tools') diff --git a/cli/tools/jupyter/install.rs b/cli/tools/jupyter/install.rs index b0ddc948d..aeff89ccf 100644 --- a/cli/tools/jupyter/install.rs +++ b/cli/tools/jupyter/install.rs @@ -58,9 +58,9 @@ pub fn install() -> Result<(), AnyError> { let f = std::fs::File::create(kernel_json_path)?; serde_json::to_writer_pretty(f, &json_data)?; - install_icon(&user_data_dir, "logo-32x32.png", DENO_ICON_32)?; - install_icon(&user_data_dir, "logo-64x64.png", DENO_ICON_64)?; - install_icon(&user_data_dir, "logo-svg.svg", DENO_ICON_SVG)?; + install_icon(&kernel_dir, "logo-32x32.png", DENO_ICON_32)?; + install_icon(&kernel_dir, "logo-64x64.png", DENO_ICON_64)?; + install_icon(&kernel_dir, "logo-svg.svg", DENO_ICON_SVG)?; log::info!("✅ Deno kernelspec installed successfully."); Ok(()) -- cgit v1.2.3 From e515f3dd0ea61bb3001e98ad7733ccb67c341f1e Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Wed, 16 Oct 2024 18:34:33 +0200 Subject: fix(add): exact version should not have range `^` specifier (#26302) Fixes https://github.com/denoland/deno/issues/26299 --- cli/tools/registry/pm.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'cli/tools') diff --git a/cli/tools/registry/pm.rs b/cli/tools/registry/pm.rs index f716dd2ca..02731d303 100644 --- a/cli/tools/registry/pm.rs +++ b/cli/tools/registry/pm.rs @@ -617,9 +617,11 @@ async fn find_package_and_select_version_for_req( }); }; let range_symbol = if req.version_req.version_text().starts_with('~') { - '~' + "~" + } else if req.version_req.version_text() == nv.version.to_string() { + "" } else { - '^' + "^" }; Ok(PackageAndVersion::Selected(SelectedPackage { import_name: add_package_req.alias, @@ -637,11 +639,15 @@ async fn find_package_and_select_version_for_req( package_req: req, }); }; + let range_symbol = if req.version_req.version_text().starts_with('~') { - '~' + "~" + } else if req.version_req.version_text() == nv.version.to_string() { + "" } else { - '^' + "^" }; + Ok(PackageAndVersion::Selected(SelectedPackage { import_name: add_package_req.alias, package_name: npm_prefixed_name, -- cgit v1.2.3 From 63f6dd355c640016b34484e0f659c00c80256826 Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Thu, 17 Oct 2024 08:05:11 -0700 Subject: fix(jupyter): update to the new logo (#26353) Follow-up to #26084 Update to the new logo found here: https://github.com/denoland/docs/blob/main/static/img/logo.svg --- cli/tools/jupyter/resources/deno-logo-32x32.png | Bin 1029 -> 1386 bytes cli/tools/jupyter/resources/deno-logo-64x64.png | Bin 2066 -> 2913 bytes cli/tools/jupyter/resources/deno-logo-svg.svg | 18 +++++++++++++++++- 3 files changed, 17 insertions(+), 1 deletion(-) (limited to 'cli/tools') diff --git a/cli/tools/jupyter/resources/deno-logo-32x32.png b/cli/tools/jupyter/resources/deno-logo-32x32.png index 97871a02e..d59f251a2 100644 Binary files a/cli/tools/jupyter/resources/deno-logo-32x32.png and b/cli/tools/jupyter/resources/deno-logo-32x32.png differ diff --git a/cli/tools/jupyter/resources/deno-logo-64x64.png b/cli/tools/jupyter/resources/deno-logo-64x64.png index 1b9444ef6..37e98abaf 100644 Binary files a/cli/tools/jupyter/resources/deno-logo-64x64.png and b/cli/tools/jupyter/resources/deno-logo-64x64.png differ diff --git a/cli/tools/jupyter/resources/deno-logo-svg.svg b/cli/tools/jupyter/resources/deno-logo-svg.svg index d7bb9ef80..fbc22cd91 100644 --- a/cli/tools/jupyter/resources/deno-logo-svg.svg +++ b/cli/tools/jupyter/resources/deno-logo-svg.svg @@ -1 +1,17 @@ - + + + + + + + + + + + -- cgit v1.2.3 From 50724d014ad6923e228e488648d40ce6f00297e9 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker <17734409+nathanwhit@users.noreply.github.com> Date: Thu, 17 Oct 2024 15:25:22 -0700 Subject: fix(install): don't attempt to cache specifiers that point to directories (#26369) Fixes https://github.com/denoland/deno/issues/26162 --- cli/tools/registry/pm/cache_deps.rs | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'cli/tools') diff --git a/cli/tools/registry/pm/cache_deps.rs b/cli/tools/registry/pm/cache_deps.rs index c8258e600..b4cd1c253 100644 --- a/cli/tools/registry/pm/cache_deps.rs +++ b/cli/tools/registry/pm/cache_deps.rs @@ -75,6 +75,13 @@ pub async fn cache_top_level_deps( if entry.key.ends_with('/') && specifier.as_str().ends_with('/') { continue; } + if specifier.scheme() == "file" { + if let Ok(path) = specifier.to_file_path() { + if !path.is_file() { + continue; + } + } + } roots.push(specifier.clone()); } } -- cgit v1.2.3 From 02e5a7a012d0c53a61f5cb38f15b92dd0db4f841 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Thu, 17 Oct 2024 23:44:14 +0100 Subject: fix(jupyter): fix panics for overslow subtraction (#26371) I don't have a reliable reproduction for it, but it makes it painful to use the Jupyter kernel with semi-frequent random panics. The completions don't always work correctly anyway, so I think it's better to just not panic here for the time being. Fixes https://github.com/denoland/deno/issues/26340 --- cli/tools/jupyter/server.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'cli/tools') diff --git a/cli/tools/jupyter/server.rs b/cli/tools/jupyter/server.rs index 0cd80f7dd..5680ed4c1 100644 --- a/cli/tools/jupyter/server.rs +++ b/cli/tools/jupyter/server.rs @@ -329,7 +329,12 @@ impl JupyterServer { }) .collect(); - (candidates, cursor_pos - prop_name.len()) + if prop_name.len() > cursor_pos { + // TODO(bartlomieju): most likely not correct, but better than panicking because of sub with overflow + (candidates, cursor_pos) + } else { + (candidates, cursor_pos - prop_name.len()) + } } else { // combine results of declarations and globalThis properties let mut candidates = get_expression_property_names( @@ -349,7 +354,12 @@ impl JupyterServer { candidates.sort(); candidates.dedup(); // make sure to sort first - (candidates, cursor_pos - expr.len()) + if expr.len() > cursor_pos { + // TODO(bartlomieju): most likely not correct, but better than panicking because of sub with overflow + (candidates, cursor_pos) + } else { + (candidates, cursor_pos - expr.len()) + } }; connection -- cgit v1.2.3 From 0e60bb9cf701fea3864403e6851abad86bc0b65d Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Fri, 18 Oct 2024 21:45:05 +0200 Subject: fix(info): resolve workspace member mappings (#26350) This PR fixes the issue where mapped specifiers in a workspace member would never be found. Only mapped paths from the workspace root would resolve. This was caused by always passing the workspace root url to the import map resolver instead of the workspace member one. Fixes https://github.com/denoland/deno/issues/26138 Fixes https://github.com/denoland/fresh/issues/2615 --------- Signed-off-by: Marvin Hagemeister Co-authored-by: David Sherret --- cli/tools/info.rs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) (limited to 'cli/tools') diff --git a/cli/tools/info.rs b/cli/tools/info.rs index 7f8d68ae1..3febaff57 100644 --- a/cli/tools/info.rs +++ b/cli/tools/info.rs @@ -11,6 +11,7 @@ use deno_core::anyhow::bail; use deno_core::error::AnyError; use deno_core::resolve_url_or_path; use deno_core::serde_json; +use deno_core::url; use deno_graph::Dependency; use deno_graph::GraphKind; use deno_graph::Module; @@ -51,18 +52,20 @@ pub async fn info( let npmrc = cli_options.npmrc(); let resolver = factory.workspace_resolver().await?; - let maybe_import_specifier = - if let Some(import_map) = resolver.maybe_import_map() { - if let Ok(imports_specifier) = - import_map.resolve(&specifier, import_map.base_url()) - { - Some(imports_specifier) - } else { - None - } + let cwd_url = + url::Url::from_directory_path(cli_options.initial_cwd()).unwrap(); + + let maybe_import_specifier = if let Some(import_map) = + resolver.maybe_import_map() + { + if let Ok(imports_specifier) = import_map.resolve(&specifier, &cwd_url) { + Some(imports_specifier) } else { None - }; + } + } else { + None + }; let specifier = match maybe_import_specifier { Some(specifier) => specifier, -- cgit v1.2.3 From 39fb55096ec12c8c18d15dff63a750a22169e3e6 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Mon, 21 Oct 2024 14:17:08 -0400 Subject: fix(install): better json editing (#26450) 1. Respects the formatting of the file (ex. keeps four space indents or tabs). 2. Handles editing of comments. 3. Handles trailing commas. 4. Code is easier to maintain. --- cli/tools/registry/pm.rs | 618 ++++++++++++++++------------------------------- 1 file changed, 208 insertions(+), 410 deletions(-) (limited to 'cli/tools') diff --git a/cli/tools/registry/pm.rs b/cli/tools/registry/pm.rs index 02731d303..bd5290998 100644 --- a/cli/tools/registry/pm.rs +++ b/cli/tools/registry/pm.rs @@ -1,32 +1,22 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -mod cache_deps; - -pub use cache_deps::cache_top_level_deps; -use deno_semver::jsr::JsrPackageReqReference; -use deno_semver::npm::NpmPackageReqReference; -use deno_semver::VersionReq; - -use std::borrow::Cow; use std::path::PathBuf; use std::sync::Arc; -use deno_ast::TextChange; -use deno_config::deno_json::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_path_util::url_to_file_path; +use deno_semver::jsr::JsrPackageReqReference; +use deno_semver::npm::NpmPackageReqReference; use deno_semver::package::PackageReq; -use indexmap::IndexMap; -use jsonc_parser::ast::ObjectProp; -use jsonc_parser::ast::Value; -use yoke::Yoke; +use deno_semver::VersionReq; +use jsonc_parser::cst::CstObject; +use jsonc_parser::cst::CstObjectProp; +use jsonc_parser::cst::CstRootNode; +use jsonc_parser::json; use crate::args::AddFlags; use crate::args::CacheSetting; @@ -38,236 +28,181 @@ use crate::file_fetcher::FileFetcher; use crate::jsr::JsrFetchResolver; use crate::npm::NpmFetchResolver; -enum DenoConfigFormat { - Json, - Jsonc, -} +mod cache_deps; -impl DenoConfigFormat { - fn from_specifier(spec: &ModuleSpecifier) -> Result { - 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}"), - } - } -} +pub use cache_deps::cache_top_level_deps; -struct DenoConfig { - config: Arc, - format: DenoConfigFormat, - imports: IndexMap, +#[derive(Debug, Copy, Clone)] +enum ConfigKind { + DenoJson, + PackageJson, } -fn deno_json_imports( - config: &deno_config::deno_json::ConfigFile, -) -> Result, AnyError> { - Ok( - config - .json - .imports - .clone() - .map(|imports| { - serde_json::from_value(imports) - .map_err(|err| anyhow!("Malformed \"imports\" configuration: {err}")) - }) - .transpose()? - .unwrap_or_default(), - ) +struct ConfigUpdater { + kind: ConfigKind, + cst: CstRootNode, + root_object: CstObject, + path: PathBuf, + modified: bool, } -impl DenoConfig { - fn from_options(options: &CliOptions) -> Result, AnyError> { - let start_dir = &options.start_dir; - if let Some(config) = start_dir.maybe_deno_json() { - Ok(Some(Self { - imports: deno_json_imports(config)?, - config: config.clone(), - format: DenoConfigFormat::from_specifier(&config.specifier)?, - })) - } else { - Ok(None) - } - } - fn add(&mut self, selected: SelectedPackage) { - self.imports.insert( - selected.import_name, - format!("{}@{}", selected.package_name, selected.version_req), - ); +impl ConfigUpdater { + fn new( + kind: ConfigKind, + config_file_path: PathBuf, + ) -> Result { + let config_file_contents = std::fs::read_to_string(&config_file_path) + .with_context(|| { + format!("Reading config file '{}'", config_file_path.display()) + })?; + let cst = CstRootNode::parse(&config_file_contents, &Default::default()) + .with_context(|| { + format!("Parsing config file '{}'", config_file_path.display()) + })?; + let root_object = cst.object_value_or_set(); + Ok(Self { + kind, + cst, + root_object, + path: config_file_path, + modified: false, + }) } - fn remove(&mut self, package: &str) -> bool { - self.imports.shift_remove(package).is_some() + fn display_path(&self) -> String { + deno_path_util::url_from_file_path(&self.path) + .map(|u| u.to_string()) + .unwrap_or_else(|_| self.path.display().to_string()) } - fn take_import_fields( - &mut self, - ) -> Vec<(&'static str, IndexMap)> { - vec![("imports", std::mem::take(&mut self.imports))] + fn obj(&self) -> &CstObject { + &self.root_object } -} -impl NpmConfig { - fn from_options(options: &CliOptions) -> Result, AnyError> { - let start_dir = &options.start_dir; - if let Some(pkg_json) = start_dir.maybe_pkg_json() { - Ok(Some(Self { - dependencies: pkg_json.dependencies.clone().unwrap_or_default(), - dev_dependencies: pkg_json.dev_dependencies.clone().unwrap_or_default(), - config: pkg_json.clone(), - fmt_options: None, - })) - } else { - Ok(None) - } + fn contents(&self) -> String { + self.cst.to_string() } fn add(&mut self, selected: SelectedPackage, dev: bool) { - let (name, version) = package_json_dependency_entry(selected); - if dev { - self.dependencies.swap_remove(&name); - self.dev_dependencies.insert(name, version); - } else { - self.dev_dependencies.swap_remove(&name); - self.dependencies.insert(name, version); + fn insert_index(object: &CstObject, searching_name: &str) -> usize { + object + .properties() + .into_iter() + .take_while(|prop| { + let prop_name = + prop.name().and_then(|name| name.decoded_value().ok()); + match prop_name { + Some(current_name) => { + searching_name.cmp(¤t_name) == std::cmp::Ordering::Greater + } + None => true, + } + }) + .count() } - } - - fn remove(&mut self, package: &str) -> bool { - let in_deps = self.dependencies.shift_remove(package).is_some(); - let in_dev_deps = self.dev_dependencies.shift_remove(package).is_some(); - in_deps || in_dev_deps - } - fn take_import_fields( - &mut self, - ) -> Vec<(&'static str, IndexMap)> { - vec![ - ("dependencies", std::mem::take(&mut self.dependencies)), - ( - "devDependencies", - std::mem::take(&mut self.dev_dependencies), - ), - ] - } -} - -struct NpmConfig { - config: Arc, - fmt_options: Option, - dependencies: IndexMap, - dev_dependencies: IndexMap, -} - -enum DenoOrPackageJson { - Deno(DenoConfig), - Npm(NpmConfig), -} - -impl From for DenoOrPackageJson { - fn from(config: DenoConfig) -> Self { - Self::Deno(config) - } -} - -impl From for DenoOrPackageJson { - fn from(config: NpmConfig) -> Self { - Self::Npm(config) - } -} + match self.kind { + ConfigKind::DenoJson => { + let imports = self.root_object.object_value_or_set("imports"); + let value = + format!("{}@{}", selected.package_name, selected.version_req); + if let Some(prop) = imports.get(&selected.import_name) { + prop.set_value(json!(value)); + } else { + let index = insert_index(&imports, &selected.import_name); + imports.insert(index, &selected.import_name, json!(value)); + } + } + ConfigKind::PackageJson => { + let deps_prop = self.root_object.get("dependencies"); + let dev_deps_prop = self.root_object.get("devDependencies"); + + let dependencies = if dev { + self + .root_object + .object_value("devDependencies") + .unwrap_or_else(|| { + let index = deps_prop + .as_ref() + .map(|p| p.property_index() + 1) + .unwrap_or_else(|| self.root_object.properties().len()); + self + .root_object + .insert(index, "devDependencies", json!({})) + .object_value_or_set() + }) + } else { + self + .root_object + .object_value("dependencies") + .unwrap_or_else(|| { + let index = dev_deps_prop + .as_ref() + .map(|p| p.property_index()) + .unwrap_or_else(|| self.root_object.properties().len()); + self + .root_object + .insert(index, "dependencies", json!({})) + .object_value_or_set() + }) + }; + let other_dependencies = if dev { + deps_prop.and_then(|p| p.value().and_then(|v| v.as_object())) + } else { + dev_deps_prop.and_then(|p| p.value().and_then(|v| v.as_object())) + }; -/// Wrapper around `jsonc_parser::ast::Object` that can be stored in a `Yoke` -#[derive(yoke::Yokeable)] -struct JsoncObjectView<'a>(jsonc_parser::ast::Object<'a>); + let (alias, value) = package_json_dependency_entry(selected); -struct ConfigUpdater { - config: DenoOrPackageJson, - // the `Yoke` is so we can carry the parsed object (which borrows from - // the source) along with the source itself - ast: Yoke, String>, - path: PathBuf, - modified: bool, -} + if let Some(other) = other_dependencies { + if let Some(prop) = other.get(&alias) { + remove_prop_and_maybe_parent_prop(prop); + } + } -impl ConfigUpdater { - fn obj(&self) -> &jsonc_parser::ast::Object<'_> { - &self.ast.get().0 - } - fn contents(&self) -> &str { - self.ast.backing_cart() - } - async fn maybe_new( - config: Option>, - ) -> Result, AnyError> { - if let Some(config) = config { - Ok(Some(Self::new(config.into()).await?)) - } else { - Ok(None) - } - } - async fn new(config: DenoOrPackageJson) -> Result { - let specifier = config.specifier(); - if specifier.scheme() != "file" { - bail!("Can't update a remote configuration file"); - } - let config_file_path = specifier.to_file_path().map_err(|_| { - anyhow!("Specifier {specifier:?} is an invalid file path") - })?; - let config_file_contents = { - let contents = tokio::fs::read_to_string(&config_file_path) - .await - .with_context(|| { - format!("Reading config file at: {}", config_file_path.display()) - })?; - if contents.trim().is_empty() { - "{}\n".into() - } else { - contents + if let Some(prop) = dependencies.get(&alias) { + prop.set_value(json!(value)); + } else { + let index = insert_index(&dependencies, &alias); + dependencies.insert(index, &alias, json!(value)); + } } - }; - let ast = Yoke::try_attach_to_cart(config_file_contents, |contents| { - let ast = jsonc_parser::parse_to_ast( - contents, - &Default::default(), - &Default::default(), - ) - .with_context(|| { - format!("Failed to parse config file at {}", specifier) - })?; - let obj = match ast.value { - Some(Value::Object(obj)) => obj, - _ => bail!( - "Failed to update config file at {}, expected an object", - specifier - ), - }; - Ok(JsoncObjectView(obj)) - })?; - Ok(Self { - config, - ast, - path: config_file_path, - modified: false, - }) - } - - fn add(&mut self, selected: SelectedPackage, dev: bool) { - match &mut self.config { - DenoOrPackageJson::Deno(deno) => deno.add(selected), - DenoOrPackageJson::Npm(npm) => npm.add(selected, dev), } + self.modified = true; } fn remove(&mut self, package: &str) -> bool { - let removed = match &mut self.config { - DenoOrPackageJson::Deno(deno) => deno.remove(package), - DenoOrPackageJson::Npm(npm) => npm.remove(package), + let removed = match self.kind { + ConfigKind::DenoJson => { + if let Some(prop) = self + .root_object + .object_value("imports") + .and_then(|i| i.get(package)) + { + remove_prop_and_maybe_parent_prop(prop); + true + } else { + false + } + } + ConfigKind::PackageJson => { + let deps = [ + self + .root_object + .object_value("dependencies") + .and_then(|deps| deps.get(package)), + self + .root_object + .object_value("devDependencies") + .and_then(|deps| deps.get(package)), + ]; + let removed = deps.iter().any(|d| d.is_some()); + for dep in deps.into_iter().flatten() { + remove_prop_and_maybe_parent_prop(dep); + } + removed + } }; if removed { self.modified = true; @@ -275,76 +210,28 @@ impl ConfigUpdater { removed } - async fn commit(mut self) -> Result<(), AnyError> { + fn commit(&self) -> Result<(), AnyError> { if !self.modified { return Ok(()); } - let import_fields = self.config.take_import_fields(); - - let fmt_config_options = self.config.fmt_options(); - - let new_text = update_config_file_content( - self.obj(), - self.contents(), - fmt_config_options, - import_fields.into_iter().map(|(k, v)| { - ( - k, - if v.is_empty() { - None - } else { - Some(generate_imports(v.into_iter().collect())) - }, - ) - }), - self.config.file_name(), - ); - - tokio::fs::write(&self.path, new_text).await?; + let new_text = self.contents(); + std::fs::write(&self.path, new_text).with_context(|| { + format!("failed writing to '{}'", self.path.display()) + })?; Ok(()) } } -impl DenoOrPackageJson { - fn specifier(&self) -> Cow { - match self { - Self::Deno(d, ..) => Cow::Borrowed(&d.config.specifier), - Self::Npm(n, ..) => Cow::Owned(n.config.specifier()), - } - } - - fn fmt_options(&self) -> FmtOptionsConfig { - match self { - DenoOrPackageJson::Deno(deno, ..) => deno - .config - .to_fmt_config() - .ok() - .map(|f| f.options) - .unwrap_or_default(), - DenoOrPackageJson::Npm(config) => { - config.fmt_options.clone().unwrap_or_default() - } - } - } - - fn take_import_fields( - &mut self, - ) -> Vec<(&'static str, IndexMap)> { - match self { - Self::Deno(d) => d.take_import_fields(), - Self::Npm(n) => n.take_import_fields(), - } - } - - fn file_name(&self) -> &'static str { - match self { - DenoOrPackageJson::Deno(config) => match config.format { - DenoConfigFormat::Json => "deno.json", - DenoConfigFormat::Jsonc => "deno.jsonc", - }, - DenoOrPackageJson::Npm(..) => "package.json", - } +fn remove_prop_and_maybe_parent_prop(prop: CstObjectProp) { + let parent = prop.parent().unwrap().as_object().unwrap(); + prop.remove(); + if parent.properties().is_empty() { + let parent_property = parent.parent().unwrap(); + let root_object = parent_property.parent().unwrap().as_object().unwrap(); + // remove the property + parent_property.remove(); + root_object.ensure_multiline(); } } @@ -401,11 +288,27 @@ impl std::fmt::Display for AddCommandName { fn load_configs( flags: &Arc, has_jsr_specifiers: impl FnOnce() -> bool, -) -> Result<(CliFactory, Option, Option), AnyError> { +) -> Result<(CliFactory, Option, Option), AnyError> +{ let cli_factory = CliFactory::from_flags(flags.clone()); let options = cli_factory.cli_options()?; - let npm_config = NpmConfig::from_options(options)?; - let (cli_factory, deno_config) = match DenoConfig::from_options(options)? { + let start_dir = &options.start_dir; + let npm_config = match start_dir.maybe_pkg_json() { + Some(pkg_json) => Some(ConfigUpdater::new( + ConfigKind::PackageJson, + pkg_json.path.clone(), + )?), + None => None, + }; + let deno_config = match start_dir.maybe_deno_json() { + Some(deno_json) => Some(ConfigUpdater::new( + ConfigKind::DenoJson, + url_to_file_path(&deno_json.specifier)?, + )?), + None => None, + }; + + let (cli_factory, deno_config) = match deno_config { Some(config) => (cli_factory, Some(config)), None if npm_config.is_some() && !has_jsr_specifiers() => { (cli_factory, None) @@ -413,11 +316,16 @@ fn load_configs( _ => { let factory = create_deno_json(flags, options)?; let options = factory.cli_options()?.clone(); + let deno_json = options + .start_dir + .maybe_deno_json() + .expect("Just created deno.json"); ( factory, - Some( - DenoConfig::from_options(&options)?.expect("Just created deno.json"), - ), + Some(ConfigUpdater::new( + ConfigKind::DenoJson, + url_to_file_path(&deno_json.specifier)?, + )?), ) } }; @@ -430,15 +338,13 @@ pub async fn add( add_flags: AddFlags, cmd_name: AddCommandName, ) -> Result<(), AnyError> { - let (cli_factory, npm_config, deno_config) = load_configs(&flags, || { - add_flags.packages.iter().any(|s| s.starts_with("jsr:")) - })?; - let mut npm_config = ConfigUpdater::maybe_new(npm_config).await?; - let mut deno_config = ConfigUpdater::maybe_new(deno_config).await?; + let (cli_factory, mut npm_config, mut deno_config) = + load_configs(&flags, || { + add_flags.packages.iter().any(|s| s.starts_with("jsr:")) + })?; if let Some(deno) = &deno_config { - let specifier = deno.config.specifier(); - if deno.obj().get_string("importMap").is_some() { + if deno.obj().get("importMap").is_some() { bail!( concat!( "`deno {}` is not supported when configuration file contains an \"importMap\" field. ", @@ -446,7 +352,7 @@ pub async fn add( " at {}", ), cmd_name, - specifier + deno.display_path(), ); } } @@ -558,18 +464,11 @@ pub async fn add( } } - let mut commit_futures = vec![]; if let Some(npm) = npm_config { - commit_futures.push(npm.commit()); + npm.commit()?; } if let Some(deno) = deno_config { - commit_futures.push(deno.commit()); - } - let commit_futures = - deno_core::futures::future::join_all(commit_futures).await; - - for result in commit_futures { - result.context("Failed to update configuration file")?; + deno.commit()?; } npm_install_after_modification(flags, Some(jsr_resolver)).await?; @@ -754,33 +653,13 @@ impl AddRmPackageReq { } } -fn generate_imports(mut packages_to_version: Vec<(String, String)>) -> String { - packages_to_version.sort_by(|(k1, _), (k2, _)| k1.cmp(k2)); - let mut contents = vec![]; - let len = packages_to_version.len(); - for (index, (package, version)) in packages_to_version.iter().enumerate() { - if index == 0 { - contents.push(String::new()); // force a newline at the start - } - // TODO(bartlomieju): fix it, once we start support specifying version on the cli - contents.push(format!("\"{}\": \"{}\"", package, version)); - if index != len - 1 { - contents.push(",".to_string()); - } - } - contents.join("\n") -} - pub async fn remove( flags: Arc, remove_flags: RemoveFlags, ) -> Result<(), AnyError> { let (_, npm_config, deno_config) = load_configs(&flags, || false)?; - let mut configs = [ - ConfigUpdater::maybe_new(npm_config).await?, - ConfigUpdater::maybe_new(deno_config).await?, - ]; + let mut configs = [npm_config, deno_config]; let mut removed_packages = vec![]; @@ -817,7 +696,7 @@ pub async fn remove( log::info!("Removed {}", crate::colors::green(package)); } for config in configs.into_iter().flatten() { - config.commit().await?; + config.commit()?; } npm_install_after_modification(flags, None).await?; @@ -847,87 +726,6 @@ async fn npm_install_after_modification( Ok(()) } -fn update_config_file_content< - I: IntoIterator)>, ->( - obj: &jsonc_parser::ast::Object, - config_file_contents: &str, - fmt_options: FmtOptionsConfig, - entries: I, - file_name: &str, -) -> String { - let mut text_changes = vec![]; - for (key, value) in entries { - match obj.properties.iter().enumerate().find_map(|(idx, k)| { - if k.name.as_str() == key { - Some((idx, k)) - } else { - None - } - }) { - Some(( - idx, - ObjectProp { - value: Value::Object(lit), - range, - .. - }, - )) => { - if let Some(value) = value { - text_changes.push(TextChange { - range: (lit.range.start + 1)..(lit.range.end - 1), - new_text: value, - }) - } else { - text_changes.push(TextChange { - // remove field entirely, making sure to - // remove the comma if it's not the last field - range: range.start..(if idx == obj.properties.len() - 1 { - range.end - } else { - obj.properties[idx + 1].range.start - }), - new_text: "".to_string(), - }) - } - } - - // need to add field - None => { - if let Some(value) = value { - let insert_position = obj.range.end - 1; - text_changes.push(TextChange { - range: insert_position..insert_position, - // NOTE(bartlomieju): adding `\n` here to force the formatter to always - // produce a config file that is multiline, like so: - // ``` - // { - // "imports": { - // "": ":@" - // } - // } - new_text: format!("\"{key}\": {{\n {value} }}"), - }) - } - } - // we verified the shape of `imports`/`dependencies` above - Some(_) => unreachable!(), - } - } - - let new_text = - deno_ast::apply_text_changes(config_file_contents, text_changes); - - crate::tools::fmt::format_json( - &PathBuf::from(file_name), - &new_text, - &fmt_options, - ) - .ok() - .map(|formatted_text| formatted_text.unwrap_or_else(|| new_text.clone())) - .unwrap_or(new_text) -} - #[cfg(test)] mod test { use super::*; -- cgit v1.2.3 From 67280f8b558902729c805fd3d8f26d4c434fc211 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Tue, 22 Oct 2024 00:08:45 +0100 Subject: fix(install): update lockfile when using package.json (#26458) This commit makes sure that `deno add`, `deno install` and `deno remove` update the lockfile if only `package.json` file is present. Fixes https://github.com/denoland/deno/issues/26270 --- cli/tools/registry/pm.rs | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'cli/tools') diff --git a/cli/tools/registry/pm.rs b/cli/tools/registry/pm.rs index bd5290998..2060b9a13 100644 --- a/cli/tools/registry/pm.rs +++ b/cli/tools/registry/pm.rs @@ -723,6 +723,10 @@ async fn npm_install_after_modification( // npm install cache_deps::cache_top_level_deps(&cli_factory, jsr_resolver).await?; + if let Some(lockfile) = cli_factory.cli_options()?.maybe_lockfile() { + lockfile.write_if_changed()?; + } + Ok(()) } -- cgit v1.2.3 From 49d31fa4a26cba20481500c9a1bd8dda53777a67 Mon Sep 17 00:00:00 2001 From: Pig Fang Date: Tue, 22 Oct 2024 18:15:59 +0800 Subject: fix(fmt): upgrade formatters (#26469) Fixes #25926 Fixes #26004 --- cli/tools/fmt.rs | 2 ++ 1 file changed, 2 insertions(+) (limited to 'cli/tools') diff --git a/cli/tools/fmt.rs b/cli/tools/fmt.rs index 8a4bc4e6c..81af25c34 100644 --- a/cli/tools/fmt.rs +++ b/cli/tools/fmt.rs @@ -978,6 +978,7 @@ fn get_resolved_malva_config( single_line_top_level_declarations: false, selector_override_comment_directive: "deno-fmt-selector-override".into(), ignore_comment_directive: "deno-fmt-ignore".into(), + ignore_file_comment_directive: "deno-fmt-ignore-file".into(), }; FormatOptions { @@ -1036,6 +1037,7 @@ fn get_resolved_markup_fmt_config( svelte_directive_shorthand: Some(true), astro_attr_shorthand: Some(true), ignore_comment_directive: "deno-fmt-ignore".into(), + ignore_file_comment_directive: "deno-fmt-ignore-file".into(), }; FormatOptions { -- cgit v1.2.3 From 6d587cbfc800a627efb32d377b260954ccd7a1ed Mon Sep 17 00:00:00 2001 From: Nathan Whitaker <17734409+nathanwhit@users.noreply.github.com> Date: Wed, 23 Oct 2024 20:12:52 -0700 Subject: fix(install): cache all exports of JSR packages listed in `deno.json` (#26501) Fixes #26498. This was a sort of intentional decision originally, as I wanted to avoid caching extra files that may not be needed. It seems like that behavior is unintuitive, so I propose we cache all of the exports of listed jsr packages when you run a bare `deno install`. --- cli/tools/registry/pm/cache_deps.rs | 4 ---- 1 file changed, 4 deletions(-) (limited to 'cli/tools') diff --git a/cli/tools/registry/pm/cache_deps.rs b/cli/tools/registry/pm/cache_deps.rs index b4cd1c253..365622d11 100644 --- a/cli/tools/registry/pm/cache_deps.rs +++ b/cli/tools/registry/pm/cache_deps.rs @@ -89,10 +89,6 @@ pub async fn cache_top_level_deps( while let Some(info_future) = info_futures.next().await { if let Some((specifier, info)) = info_future { - if info.export(".").is_some() { - roots.push(specifier.clone()); - continue; - } let exports = info.exports(); for (k, _) in exports { if let Ok(spec) = specifier.join(k) { -- cgit v1.2.3 From 5f0bb3c6f4328003012e98ba70ce18e4e2e842de Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Thu, 24 Oct 2024 20:03:56 +0200 Subject: fix: `.npmrc` settings not being passed to install/add command (#26473) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We weren't passing the resolved npmrc settings to the install commands. This lead us to always fall back to the default registry url instead of using the one from npmrc. Fixes https://github.com/denoland/deno/issues/26139 Fixes https://github.com/denoland/deno/issues/26033 Fixes https://github.com/denoland/deno/issues/25924 Fixes https://github.com/denoland/deno/issues/25822 Fixes https://github.com/denoland/deno/issues/26152 --------- Co-authored-by: Bartek Iwańczuk --- cli/tools/registry/pm.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'cli/tools') diff --git a/cli/tools/registry/pm.rs b/cli/tools/registry/pm.rs index 2060b9a13..d1be901d6 100644 --- a/cli/tools/registry/pm.rs +++ b/cli/tools/registry/pm.rs @@ -367,10 +367,14 @@ pub async fn add( Default::default(), None, ); + + let npmrc = cli_factory.cli_options().unwrap().npmrc(); + deps_file_fetcher.set_download_log_level(log::Level::Trace); let deps_file_fetcher = Arc::new(deps_file_fetcher); let jsr_resolver = Arc::new(JsrFetchResolver::new(deps_file_fetcher.clone())); - let npm_resolver = Arc::new(NpmFetchResolver::new(deps_file_fetcher)); + let npm_resolver = + Arc::new(NpmFetchResolver::new(deps_file_fetcher, npmrc.clone())); let mut selected_packages = Vec::with_capacity(add_flags.packages.len()); let mut package_reqs = Vec::with_capacity(add_flags.packages.len()); -- cgit v1.2.3 From eedf243b5ea98d22649bb0445444719a2fc12c59 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Thu, 24 Oct 2024 15:48:48 -0400 Subject: perf(compile): pass module source data from binary directly to v8 (#26494) This changes denort to pass a static reference of the moude source bytes found in the binary to v8 instead of copying it. --- cli/tools/compile.rs | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) (limited to 'cli/tools') diff --git a/cli/tools/compile.rs b/cli/tools/compile.rs index 3cc4414fc..5a4a938bb 100644 --- a/cli/tools/compile.rs +++ b/cli/tools/compile.rs @@ -5,6 +5,7 @@ use crate::args::CompileFlags; use crate::args::Flags; use crate::factory::CliFactory; use crate::http_util::HttpClientProvider; +use crate::standalone::binary::StandaloneRelativeFileBaseUrl; use crate::standalone::is_standalone_binary; use deno_ast::ModuleSpecifier; use deno_core::anyhow::bail; @@ -14,7 +15,6 @@ use deno_core::error::AnyError; use deno_core::resolve_url_or_path; use deno_graph::GraphKind; use deno_terminal::colors; -use eszip::EszipRelativeFileBaseUrl; use rand::Rng; use std::path::Path; use std::path::PathBuf; @@ -29,7 +29,6 @@ pub async fn compile( let factory = CliFactory::from_flags(flags); let cli_options = factory.cli_options()?; let module_graph_creator = factory.module_graph_creator().await?; - let parsed_source_cache = factory.parsed_source_cache(); let binary_writer = factory.create_compile_binary_writer().await?; let http_client = factory.http_client_provider(); let module_specifier = cli_options.resolve_main_module()?; @@ -80,7 +79,7 @@ pub async fn compile( let graph = if cli_options.type_check_mode().is_true() { // In this case, the previous graph creation did type checking, which will // create a module graph with types information in it. We don't want to - // store that in the eszip so create a code only module graph from scratch. + // store that in the binary so create a code only module graph from scratch. module_graph_creator .create_graph(GraphKind::CodeOnly, module_roots) .await? @@ -91,11 +90,6 @@ pub async fn compile( let ts_config_for_emit = cli_options .resolve_ts_config_for_emit(deno_config::deno_json::TsConfigType::Emit)?; check_warn_tsconfig(&ts_config_for_emit); - let (transpile_options, emit_options) = - crate::args::ts_config_to_transpile_and_emit_options( - ts_config_for_emit.ts_config, - )?; - let parser = parsed_source_cache.as_capturing_parser(); let root_dir_url = resolve_root_dir_from_specifiers( cli_options.workspace().root_dir(), graph.specifiers().map(|(s, _)| s).chain( @@ -106,17 +100,6 @@ pub async fn compile( ), ); log::debug!("Binary root dir: {}", root_dir_url); - let root_dir_url = EszipRelativeFileBaseUrl::new(&root_dir_url); - let eszip = eszip::EszipV2::from_graph(eszip::FromGraphOptions { - graph, - parser, - transpile_options, - emit_options, - // make all the modules relative to the root folder - relative_file_base: Some(root_dir_url), - npm_packages: None, - })?; - log::info!( "{} {} to {}", colors::green("Compile"), @@ -143,15 +126,18 @@ pub async fn compile( let write_result = binary_writer .write_bin( file, - eszip, - root_dir_url, + &graph, + StandaloneRelativeFileBaseUrl::from(&root_dir_url), module_specifier, &compile_flags, cli_options, ) .await .with_context(|| { - format!("Writing temporary file '{}'", temp_path.display()) + format!( + "Writing deno compile executable to temporary file '{}'", + temp_path.display() + ) }); // set it as executable -- cgit v1.2.3 From 0060e74779e25eba4544ca540066f07a1c46d17b Mon Sep 17 00:00:00 2001 From: Nathan Whitaker <17734409+nathanwhit@users.noreply.github.com> Date: Thu, 24 Oct 2024 16:46:48 -0700 Subject: fix(install): don't cache json exports of JSR packages (for now) (#26530) Temporary fix for #26509, so people don't get errors. --- cli/tools/registry/pm/cache_deps.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'cli/tools') diff --git a/cli/tools/registry/pm/cache_deps.rs b/cli/tools/registry/pm/cache_deps.rs index 365622d11..9883deb1d 100644 --- a/cli/tools/registry/pm/cache_deps.rs +++ b/cli/tools/registry/pm/cache_deps.rs @@ -90,8 +90,13 @@ pub async fn cache_top_level_deps( while let Some(info_future) = info_futures.next().await { if let Some((specifier, info)) = info_future { let exports = info.exports(); - for (k, _) in exports { + for (k, v) in exports { if let Ok(spec) = specifier.join(k) { + if v.ends_with(".json") { + // TODO(nathanwhit): this should work, there's a bug with + // json roots in deno_graph. skip it for now + continue; + } roots.push(spec); } } -- cgit v1.2.3 From a01edb394d2785b7d37da6435bb37381efc376ea Mon Sep 17 00:00:00 2001 From: David Sherret Date: Fri, 25 Oct 2024 15:58:28 -0400 Subject: fix(upgrade): stop running `deno lsp` processes on windows before attempting to replace executable (#26542) --- cli/tools/upgrade.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) (limited to 'cli/tools') diff --git a/cli/tools/upgrade.rs b/cli/tools/upgrade.rs index b1b09d1a6..77a9f72b8 100644 --- a/cli/tools/upgrade.rs +++ b/cli/tools/upgrade.rs @@ -579,6 +579,10 @@ pub async fn upgrade( let output_exe_path = full_path_output_flag.as_ref().unwrap_or(¤t_exe_path); + + #[cfg(windows)] + kill_running_deno_lsp_processes(); + let output_result = if *output_exe_path == current_exe_path { replace_exe(&new_exe_path, output_exe_path) } else { @@ -966,6 +970,34 @@ fn check_windows_access_denied_error( }) } +#[cfg(windows)] +fn kill_running_deno_lsp_processes() { + // limit this to `deno lsp` invocations to avoid killing important programs someone might be running + let is_debug = log::log_enabled!(log::Level::Debug); + let get_pipe = || { + if is_debug { + std::process::Stdio::inherit() + } else { + std::process::Stdio::null() + } + }; + let _ = Command::new("powershell.exe") + .args([ + "-Command", + r#"Get-WmiObject Win32_Process | Where-Object { + $_.Name -eq 'deno.exe' -and + $_.CommandLine -match '^(?:\"[^\"]+\"|\S+)\s+lsp\b' +} | ForEach-Object { + if ($_.Terminate()) { + Write-Host 'Terminated:' $_.ProcessId + } +}"#, + ]) + .stdout(get_pipe()) + .stderr(get_pipe()) + .output(); +} + fn set_exe_permissions( current_exe_path: &Path, output_exe_path: &Path, -- cgit v1.2.3 From ec968aa5aec068e92fb554fc7192d912bcddb82c Mon Sep 17 00:00:00 2001 From: Nathan Whitaker <17734409+nathanwhit@users.noreply.github.com> Date: Fri, 25 Oct 2024 13:19:03 -0700 Subject: fix(install): cache json exports of JSR packages (#26552) Fixes https://github.com/denoland/deno/issues/26509. Ended up being a `deno_graph` bug causing the error to surface. This PR updates `deno_graph` to pick up the fix and reverts the temporary workaround that skipped JSON exports. --- cli/tools/registry/pm/cache_deps.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'cli/tools') diff --git a/cli/tools/registry/pm/cache_deps.rs b/cli/tools/registry/pm/cache_deps.rs index 9883deb1d..365622d11 100644 --- a/cli/tools/registry/pm/cache_deps.rs +++ b/cli/tools/registry/pm/cache_deps.rs @@ -90,13 +90,8 @@ pub async fn cache_top_level_deps( while let Some(info_future) = info_futures.next().await { if let Some((specifier, info)) = info_future { let exports = info.exports(); - for (k, v) in exports { + for (k, _) in exports { if let Ok(spec) = specifier.join(k) { - if v.ends_with(".json") { - // TODO(nathanwhit): this should work, there's a bug with - // json roots in deno_graph. skip it for now - continue; - } roots.push(spec); } } -- cgit v1.2.3 From f0f476e58453d502ab5c118fc91d1475e6829967 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Sat, 26 Oct 2024 13:41:09 -0400 Subject: perf: pass transpiled module to deno_core as known string (#26555) --- cli/tools/coverage/mod.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'cli/tools') diff --git a/cli/tools/coverage/mod.rs b/cli/tools/coverage/mod.rs index 260c0c842..3b08f2c77 100644 --- a/cli/tools/coverage/mod.rs +++ b/cli/tools/coverage/mod.rs @@ -571,7 +571,7 @@ pub async fn cover_files( | MediaType::Cjs | MediaType::Mjs | MediaType::Json => None, - MediaType::Dts | MediaType::Dmts | MediaType::Dcts => Some(Vec::new()), + MediaType::Dts | MediaType::Dmts | MediaType::Dcts => Some(String::new()), MediaType::TypeScript | MediaType::Jsx | MediaType::Mts @@ -593,8 +593,7 @@ pub async fn cover_files( } }; let runtime_code: String = match transpiled_code { - Some(code) => String::from_utf8(code) - .with_context(|| format!("Failed decoding {}", file.specifier))?, + Some(code) => code, None => original_source.to_string(), }; -- cgit v1.2.3 From aa2a354190f15645a7db0563eebe49255a083ff9 Mon Sep 17 00:00:00 2001 From: Yoshiya Hinosawa Date: Tue, 29 Oct 2024 14:37:21 +0900 Subject: refactor(init): inline routing in deno init --serve template (#26595) --- cli/tools/init/mod.rs | 41 +++++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 22 deletions(-) (limited to 'cli/tools') diff --git a/cli/tools/init/mod.rs b/cli/tools/init/mod.rs index 2d6a894e1..4e4a686c5 100644 --- a/cli/tools/init/mod.rs +++ b/cli/tools/init/mod.rs @@ -24,32 +24,29 @@ pub fn init_project(init_flags: InitFlags) -> Result<(), AnyError> { create_file( &dir, "main.ts", - r#"import { type Route, route, serveDir } from "@std/http"; + r#"import { serveDir } from "@std/http"; -const routes: Route[] = [ - { - pattern: new URLPattern({ pathname: "/" }), - handler: () => new Response("Home page"), - }, - { - pattern: new URLPattern({ pathname: "/users/:id" }), - handler: (_req, _info, params) => new Response(params?.pathname.groups.id), - }, - { - pattern: new URLPattern({ pathname: "/static/*" }), - handler: (req) => serveDir(req), - }, -]; - -function defaultHandler(_req: Request) { - return new Response("Not found", { status: 404 }); -} - -const handler = route(routes, defaultHandler); +const userPagePattern = new URLPattern({ pathname: "/users/:id" }); +const staticPathPattern = new URLPattern({ pathname: "/static/*" }); export default { fetch(req) { - return handler(req); + const url = new URL(req.url); + + if (url.pathname === "/") { + return new Response("Home page"); + } + + const userPageMatch = userPagePattern.exec(url); + if (userPageMatch) { + return new Response(userPageMatch.pathname.groups.id); + } + + if (staticPathPattern.test(url)) { + return serveDir(req); + } + + return new Response("Not found", { status: 404 }); }, } satisfies Deno.ServeDefaultExport; "#, -- cgit v1.2.3 From 5f5ac4063142c6c411486581f2f26cf51a32d7e3 Mon Sep 17 00:00:00 2001 From: HasanAlrimawi <141642411+HasanAlrimawi@users.noreply.github.com> Date: Wed, 30 Oct 2024 19:32:18 +0200 Subject: fix(serve): support serve hmr (#26078) This PR addresses issue #25600 Changes: Updated `fn has_hmr` to check `serve` subcommand and return its hmr value if found, in order to run the worker in serve mode with hmr_runner. Thus the hmr event can be dispatched upon changes on the file served. --- cli/tools/serve.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'cli/tools') diff --git a/cli/tools/serve.rs b/cli/tools/serve.rs index 4ce1cad6f..e3f9e94f8 100644 --- a/cli/tools/serve.rs +++ b/cli/tools/serve.rs @@ -44,12 +44,15 @@ pub async fn serve( maybe_npm_install(&factory).await?; let worker_factory = factory.create_cli_main_worker_factory().await?; - + let hmr = serve_flags + .watch + .map(|watch_flags| watch_flags.hmr) + .unwrap_or(false); do_serve( worker_factory, main_module.clone(), serve_flags.worker_count, - false, + hmr, ) .await } @@ -109,8 +112,6 @@ async fn do_serve( } } Ok(exit_code) - - // main.await? } async fn run_worker( @@ -119,7 +120,7 @@ async fn run_worker( main_module: ModuleSpecifier, hmr: bool, ) -> Result { - let mut worker = worker_factory + let mut worker: crate::worker::CliMainWorker = worker_factory .create_main_worker( deno_runtime::WorkerExecutionMode::Serve { is_main: false, -- cgit v1.2.3 From 50ea707b58dff85b479905ebbeef866c95bc3cad Mon Sep 17 00:00:00 2001 From: Taku Amano Date: Thu, 31 Oct 2024 23:20:26 +0900 Subject: fix(coverage): exclude comment lines from coverage reports (#25939) --- cli/tools/coverage/mod.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) (limited to 'cli/tools') diff --git a/cli/tools/coverage/mod.rs b/cli/tools/coverage/mod.rs index 3b08f2c77..48922f144 100644 --- a/cli/tools/coverage/mod.rs +++ b/cli/tools/coverage/mod.rs @@ -327,6 +327,7 @@ fn generate_coverage_report( coverage_report.found_lines = if let Some(source_map) = maybe_source_map.as_ref() { + let script_source_lines = script_source.lines().collect::>(); let mut found_lines = line_counts .iter() .enumerate() @@ -334,7 +335,23 @@ fn generate_coverage_report( // get all the mappings from this destination line to a different src line let mut results = source_map .tokens() - .filter(move |token| token.get_dst_line() as usize == index) + .filter(|token| { + let dst_line = token.get_dst_line() as usize; + dst_line == index && { + let dst_col = token.get_dst_col() as usize; + let content = script_source_lines + .get(dst_line) + .and_then(|line| { + line.get(dst_col..std::cmp::min(dst_col + 2, line.len())) + }) + .unwrap_or(""); + + !content.is_empty() + && content != "/*" + && content != "*/" + && content != "//" + } + }) .map(move |token| (token.get_src_line() as usize, *count)) .collect::>(); // only keep the results that point at different src lines -- cgit v1.2.3 From 04ae1a551726dd6e0047a942b459d18e1dcb1935 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker <17734409+nathanwhit@users.noreply.github.com> Date: Thu, 31 Oct 2024 22:19:19 -0700 Subject: fix(cli): set `npm_config_user_agent` when running npm packages or tasks (#26639) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #25342. Still not sure on the exact user agent to set (should it include `node`?). After this PR, here's the state of running some `create-*` packages (just ones I could think of off the top of my head): | package | prints/runs/suggests deno install | notes | | ---------------- | ------------- | ------ | | `create-next-app` | ❌ | falls back to npm, needs a PR ([code](https://github.com/vercel/next.js/blob/c32e2802097c03fd9f95b1dae228d6e0257569c0/packages/create-next-app/helpers/get-pkg-manager.ts#L3)) | `sv create` | ❌ | uses `package-manager-detector`, needs a PR ([code](https://github.com/antfu-collective/package-manager-detector/tree/main)) | `create-qwik` | ✅ | runs `deno install` but suggests `deno start` which doesn't work (should be `deno task start` or `deno run start`) | `create-astro` | ✅ | runs `deno install` but suggests `npm run dev` later in output, probably needs a PR | `nuxi init` | ❌ | deno not an option in dialog, needs a PR ([code](https://github.com/nuxt/cli/blob/f04e2e894446f597da9d971b7eb03191d5a0da7e/src/commands/init.ts#L96-L102)) | `create-react-app` | ❌ | uses npm | `ng new` (`@angular/cli`) | ❌ | uses npm | `create-vite` | ✅ | suggests working deno commands 🎉 | `create-solid` | ❌ | suggests npm commands, needs PR It's possible that fixing `package-manager-detector` or other packages might make some of these just work, but haven't looked too carefully at each --- cli/tools/run/mod.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'cli/tools') diff --git a/cli/tools/run/mod.rs b/cli/tools/run/mod.rs index 152e2650b..bebb3f588 100644 --- a/cli/tools/run/mod.rs +++ b/cli/tools/run/mod.rs @@ -30,6 +30,16 @@ To grant permissions, set them before the script argument. For example: } } +fn set_npm_user_agent() { + static ONCE: std::sync::Once = std::sync::Once::new(); + ONCE.call_once(|| { + std::env::set_var( + crate::npm::NPM_CONFIG_USER_AGENT_ENV_VAR, + crate::npm::get_npm_config_user_agent(), + ); + }); +} + pub async fn run_script( mode: WorkerExecutionMode, flags: Arc, @@ -58,6 +68,10 @@ pub async fn run_script( let main_module = cli_options.resolve_main_module()?; + if main_module.scheme() == "npm" { + set_npm_user_agent(); + } + maybe_npm_install(&factory).await?; let worker_factory = factory.create_cli_main_worker_factory().await?; @@ -119,6 +133,10 @@ async fn run_with_watch( let cli_options = factory.cli_options()?; let main_module = cli_options.resolve_main_module()?; + if main_module.scheme() == "npm" { + set_npm_user_agent(); + } + maybe_npm_install(&factory).await?; let _ = watcher_communicator.watch_paths(cli_options.watch_paths()); -- cgit v1.2.3 From 4774eab64d5176e997b6431f03f075782321b3d9 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Fri, 1 Nov 2024 16:13:02 +0530 Subject: chore: upgrade to rust 1.82 and LLVM 19 (#26615) Upgrade to rust 1.82 and LLVM 19 . Removes one webusb test because `requestAdapter` not working on new ubuntu 24 runners --- cli/tools/info.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'cli/tools') diff --git a/cli/tools/info.rs b/cli/tools/info.rs index 3febaff57..b53485bd6 100644 --- a/cli/tools/info.rs +++ b/cli/tools/info.rs @@ -530,7 +530,7 @@ impl<'a> GraphDisplayContext<'a> { fn build_module_info(&mut self, module: &Module, type_dep: bool) -> TreeNode { enum PackageOrSpecifier { - Package(NpmResolutionPackage), + Package(Box), Specifier(ModuleSpecifier), } @@ -538,7 +538,7 @@ impl<'a> GraphDisplayContext<'a> { let package_or_specifier = match module.npm() { Some(npm) => match self.npm_info.resolve_package(npm.nv_reference.nv()) { - Some(package) => Package(package.clone()), + Some(package) => Package(Box::new(package.clone())), None => Specifier(module.specifier().clone()), // should never happen }, None => Specifier(module.specifier().clone()), -- cgit v1.2.3 From 826e42a5b5880c974ae019a7a21aade6a718062c Mon Sep 17 00:00:00 2001 From: David Sherret Date: Fri, 1 Nov 2024 12:27:00 -0400 Subject: fix: improved support for cjs and cts modules (#26558) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * cts support * better cjs/cts type checking * deno compile cjs/cts support * More efficient detect cjs (going towards stabilization) * Determination of whether .js, .ts, .jsx, or .tsx is cjs or esm is only done after loading * Support `import x = require(...);` Co-authored-by: Bartek Iwańczuk --- cli/tools/bench/mod.rs | 2 +- cli/tools/check.rs | 11 ++++++++--- cli/tools/compile.rs | 10 ---------- cli/tools/coverage/mod.rs | 23 +++++++++++++++-------- cli/tools/doc.rs | 6 +++--- cli/tools/registry/tar.rs | 2 +- cli/tools/repl/session.rs | 6 +++++- cli/tools/run/hmr.rs | 25 +++++++++++++++++-------- cli/tools/test/mod.rs | 2 +- 9 files changed, 51 insertions(+), 36 deletions(-) (limited to 'cli/tools') diff --git a/cli/tools/bench/mod.rs b/cli/tools/bench/mod.rs index be5d0ad0e..272d06335 100644 --- a/cli/tools/bench/mod.rs +++ b/cli/tools/bench/mod.rs @@ -193,7 +193,7 @@ async fn bench_specifier_inner( .await?; // We execute the main module as a side module so that import.meta.main is not set. - worker.execute_side_module_possibly_with_npm().await?; + worker.execute_side_module().await?; let mut worker = worker.into_main_worker(); diff --git a/cli/tools/check.rs b/cli/tools/check.rs index 7edb392d4..d88027888 100644 --- a/cli/tools/check.rs +++ b/cli/tools/check.rs @@ -32,6 +32,7 @@ use crate::graph_util::ModuleGraphBuilder; use crate::npm::CliNpmResolver; use crate::tsc; use crate::tsc::Diagnostics; +use crate::tsc::TypeCheckingCjsTracker; use crate::util::extract; use crate::util::path::to_percent_decoded_str; @@ -99,6 +100,7 @@ pub struct CheckOptions { pub struct TypeChecker { caches: Arc, + cjs_tracker: Arc, cli_options: Arc, module_graph_builder: Arc, node_resolver: Arc, @@ -108,6 +110,7 @@ pub struct TypeChecker { impl TypeChecker { pub fn new( caches: Arc, + cjs_tracker: Arc, cli_options: Arc, module_graph_builder: Arc, node_resolver: Arc, @@ -115,6 +118,7 @@ impl TypeChecker { ) -> Self { Self { caches, + cjs_tracker, cli_options, module_graph_builder, node_resolver, @@ -244,6 +248,7 @@ impl TypeChecker { graph: graph.clone(), hash_data, maybe_npm: Some(tsc::RequestNpmState { + cjs_tracker: self.cjs_tracker.clone(), node_resolver: self.node_resolver.clone(), npm_resolver: self.npm_resolver.clone(), }), @@ -346,7 +351,7 @@ fn get_check_hash( } } MediaType::Json - | MediaType::TsBuildInfo + | MediaType::Css | MediaType::SourceMap | MediaType::Wasm | MediaType::Unknown => continue, @@ -428,7 +433,7 @@ fn get_tsc_roots( } MediaType::Json | MediaType::Wasm - | MediaType::TsBuildInfo + | MediaType::Css | MediaType::SourceMap | MediaType::Unknown => None, }, @@ -536,7 +541,7 @@ fn has_ts_check(media_type: MediaType, file_text: &str) -> bool { | MediaType::Tsx | MediaType::Json | MediaType::Wasm - | MediaType::TsBuildInfo + | MediaType::Css | MediaType::SourceMap | MediaType::Unknown => false, } diff --git a/cli/tools/compile.rs b/cli/tools/compile.rs index 5a4a938bb..b3e999337 100644 --- a/cli/tools/compile.rs +++ b/cli/tools/compile.rs @@ -53,16 +53,6 @@ pub async fn compile( ); } - if cli_options.unstable_detect_cjs() { - log::warn!( - concat!( - "{} --unstable-detect-cjs is not properly supported in deno compile. ", - "The compiled executable may encounter runtime errors.", - ), - crate::colors::yellow("Warning"), - ); - } - let output_path = resolve_compile_executable_output_path( http_client, &compile_flags, diff --git a/cli/tools/coverage/mod.rs b/cli/tools/coverage/mod.rs index 48922f144..f59333247 100644 --- a/cli/tools/coverage/mod.rs +++ b/cli/tools/coverage/mod.rs @@ -6,12 +6,12 @@ use crate::args::FileFlags; use crate::args::Flags; use crate::cdp; use crate::factory::CliFactory; -use crate::npm::CliNpmResolver; use crate::tools::fmt::format_json; use crate::tools::test::is_supported_test_path; use crate::util::text_encoding::source_map_from_code; use deno_ast::MediaType; +use deno_ast::ModuleKind; use deno_ast::ModuleSpecifier; use deno_config::glob::FileCollector; use deno_config::glob::FilePatterns; @@ -25,6 +25,7 @@ use deno_core::serde_json; use deno_core::sourcemap::SourceMap; use deno_core::url::Url; use deno_core::LocalInspectorSession; +use node_resolver::InNpmPackageChecker; use regex::Regex; use std::fs; use std::fs::File; @@ -461,7 +462,7 @@ fn filter_coverages( coverages: Vec, include: Vec, exclude: Vec, - npm_resolver: &dyn CliNpmResolver, + in_npm_pkg_checker: &dyn InNpmPackageChecker, ) -> Vec { let include: Vec = include.iter().map(|e| Regex::new(e).unwrap()).collect(); @@ -485,7 +486,7 @@ fn filter_coverages( || doc_test_re.is_match(e.url.as_str()) || Url::parse(&e.url) .ok() - .map(|url| npm_resolver.in_npm_package(&url)) + .map(|url| in_npm_pkg_checker.in_npm_package(&url)) .unwrap_or(false); let is_included = include.iter().any(|p| p.is_match(&e.url)); @@ -496,7 +497,7 @@ fn filter_coverages( .collect::>() } -pub async fn cover_files( +pub fn cover_files( flags: Arc, coverage_flags: CoverageFlags, ) -> Result<(), AnyError> { @@ -506,9 +507,10 @@ pub async fn cover_files( let factory = CliFactory::from_flags(flags); let cli_options = factory.cli_options()?; - let npm_resolver = factory.npm_resolver().await?; + let in_npm_pkg_checker = factory.in_npm_pkg_checker()?; let file_fetcher = factory.file_fetcher()?; let emitter = factory.emitter()?; + let cjs_tracker = factory.cjs_tracker()?; assert!(!coverage_flags.files.include.is_empty()); @@ -528,7 +530,7 @@ pub async fn cover_files( script_coverages, coverage_flags.include, coverage_flags.exclude, - npm_resolver.as_ref(), + in_npm_pkg_checker.as_ref(), ); if script_coverages.is_empty() { return Err(generic_error("No covered files included in the report")); @@ -585,6 +587,8 @@ pub async fn cover_files( let transpiled_code = match file.media_type { MediaType::JavaScript | MediaType::Unknown + | MediaType::Css + | MediaType::Wasm | MediaType::Cjs | MediaType::Mjs | MediaType::Json => None, @@ -594,7 +598,10 @@ pub async fn cover_files( | MediaType::Mts | MediaType::Cts | MediaType::Tsx => { - Some(match emitter.maybe_cached_emit(&file.specifier, &file.source) { + let module_kind = ModuleKind::from_is_cjs( + cjs_tracker.is_maybe_cjs(&file.specifier, file.media_type)?, + ); + Some(match emitter.maybe_cached_emit(&file.specifier, module_kind, &file.source) { Some(code) => code, None => { return Err(anyhow!( @@ -605,7 +612,7 @@ pub async fn cover_files( } }) } - MediaType::Wasm | MediaType::TsBuildInfo | MediaType::SourceMap => { + MediaType::SourceMap => { unreachable!() } }; diff --git a/cli/tools/doc.rs b/cli/tools/doc.rs index 5e18546a2..e33da4efb 100644 --- a/cli/tools/doc.rs +++ b/cli/tools/doc.rs @@ -22,9 +22,9 @@ use deno_core::serde_json; use deno_doc as doc; use deno_doc::html::UrlResolveKind; use deno_graph::source::NullFileSystem; +use deno_graph::EsParser; use deno_graph::GraphKind; use deno_graph::ModuleAnalyzer; -use deno_graph::ModuleParser; use deno_graph::ModuleSpecifier; use doc::html::ShortPath; use doc::DocDiagnostic; @@ -37,7 +37,7 @@ const JSON_SCHEMA_VERSION: u8 = 1; async fn generate_doc_nodes_for_builtin_types( doc_flags: DocFlags, - parser: &dyn ModuleParser, + parser: &dyn EsParser, analyzer: &dyn ModuleAnalyzer, ) -> Result>, AnyError> { let source_file_specifier = @@ -96,7 +96,7 @@ pub async fn doc( let module_info_cache = factory.module_info_cache()?; let parsed_source_cache = factory.parsed_source_cache(); let capturing_parser = parsed_source_cache.as_capturing_parser(); - let analyzer = module_info_cache.as_module_analyzer(parsed_source_cache); + let analyzer = module_info_cache.as_module_analyzer(); let doc_nodes_by_url = match doc_flags.source_files { DocSourceFileFlag::Builtin => { diff --git a/cli/tools/registry/tar.rs b/cli/tools/registry/tar.rs index aca125e00..6d1801ce6 100644 --- a/cli/tools/registry/tar.rs +++ b/cli/tools/registry/tar.rs @@ -120,7 +120,7 @@ fn resolve_content_maybe_unfurling( | MediaType::Unknown | MediaType::Json | MediaType::Wasm - | MediaType::TsBuildInfo => { + | MediaType::Css => { // not unfurlable data return Ok(data); } diff --git a/cli/tools/repl/session.rs b/cli/tools/repl/session.rs index 484664dae..23b0f11ac 100644 --- a/cli/tools/repl/session.rs +++ b/cli/tools/repl/session.rs @@ -25,6 +25,7 @@ use deno_ast::swc::visit::noop_visit_type; use deno_ast::swc::visit::Visit; use deno_ast::swc::visit::VisitWith; use deno_ast::ImportsNotUsedAsValues; +use deno_ast::ModuleKind; use deno_ast::ModuleSpecifier; use deno_ast::ParseDiagnosticsError; use deno_ast::ParsedSource; @@ -641,6 +642,10 @@ impl ReplSession { jsx_fragment_factory: self.jsx.frag_factory.clone(), jsx_import_source: self.jsx.import_source.clone(), var_decl_imports: true, + verbatim_module_syntax: false, + }, + &deno_ast::TranspileModuleOptions { + module_kind: Some(ModuleKind::Esm), }, &deno_ast::EmitOptions { source_map: deno_ast::SourceMapOption::None, @@ -651,7 +656,6 @@ impl ReplSession { }, )? .into_source() - .into_string()? .text; let value = self diff --git a/cli/tools/run/hmr.rs b/cli/tools/run/hmr.rs index 6ccf8e344..6cebedd01 100644 --- a/cli/tools/run/hmr.rs +++ b/cli/tools/run/hmr.rs @@ -1,9 +1,11 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use crate::cdp; -use crate::emit::Emitter; -use crate::util::file_watcher::WatcherCommunicator; -use crate::util::file_watcher::WatcherRestartMode; +use std::collections::HashMap; +use std::path::PathBuf; +use std::sync::Arc; + +use deno_ast::MediaType; +use deno_ast::ModuleKind; use deno_core::error::generic_error; use deno_core::error::AnyError; use deno_core::futures::StreamExt; @@ -12,11 +14,14 @@ use deno_core::serde_json::{self}; use deno_core::url::Url; use deno_core::LocalInspectorSession; use deno_terminal::colors; -use std::collections::HashMap; -use std::path::PathBuf; -use std::sync::Arc; use tokio::select; +use crate::cdp; +use crate::emit::Emitter; +use crate::resolver::CjsTracker; +use crate::util::file_watcher::WatcherCommunicator; +use crate::util::file_watcher::WatcherRestartMode; + fn explain(status: &cdp::Status) -> &'static str { match status { cdp::Status::Ok => "OK", @@ -58,6 +63,7 @@ pub struct HmrRunner { session: LocalInspectorSession, watcher_communicator: Arc, script_ids: HashMap, + cjs_tracker: Arc, emitter: Arc, } @@ -139,7 +145,8 @@ impl crate::worker::HmrRunner for HmrRunner { }; let source_code = self.emitter.load_and_emit_for_hmr( - &module_url + &module_url, + ModuleKind::from_is_cjs(self.cjs_tracker.is_maybe_cjs(&module_url, MediaType::from_specifier(&module_url))?), ).await?; let mut tries = 1; @@ -172,12 +179,14 @@ impl crate::worker::HmrRunner for HmrRunner { impl HmrRunner { pub fn new( + cjs_tracker: Arc, emitter: Arc, session: LocalInspectorSession, watcher_communicator: Arc, ) -> Self { Self { session, + cjs_tracker, emitter, watcher_communicator, script_ids: HashMap::new(), diff --git a/cli/tools/test/mod.rs b/cli/tools/test/mod.rs index e81abad0b..fa849614f 100644 --- a/cli/tools/test/mod.rs +++ b/cli/tools/test/mod.rs @@ -631,7 +631,7 @@ async fn configure_main_worker( "Deno[Deno.internal].core.setLeakTracingEnabled(true);", )?; } - let res = worker.execute_side_module_possibly_with_npm().await; + let res = worker.execute_side_module().await; let mut worker = worker.into_main_worker(); match res { Ok(()) => Ok(()), -- cgit v1.2.3 From 2c8a0e791732ab00907ca11c3a4918ad26ead03f Mon Sep 17 00:00:00 2001 From: Nathan Whitaker <17734409+nathanwhit@users.noreply.github.com> Date: Fri, 1 Nov 2024 19:10:35 -0700 Subject: fix(add): only add npm deps to package.json if it's at least as close as deno.json (#26683) Fixes https://github.com/denoland/deno/issues/26653 --- cli/tools/registry/pm.rs | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) (limited to 'cli/tools') diff --git a/cli/tools/registry/pm.rs b/cli/tools/registry/pm.rs index d1be901d6..4e0387998 100644 --- a/cli/tools/registry/pm.rs +++ b/cli/tools/registry/pm.rs @@ -1,5 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use std::path::Path; use std::path::PathBuf; use std::sync::Arc; @@ -333,6 +334,14 @@ fn load_configs( Ok((cli_factory, npm_config, deno_config)) } +fn path_distance(a: &Path, b: &Path) -> usize { + let diff = pathdiff::diff_paths(a, b); + let Some(diff) = diff else { + return usize::MAX; + }; + diff.components().count() +} + pub async fn add( flags: Arc, add_flags: AddFlags, @@ -357,6 +366,21 @@ pub async fn add( } } + let start_dir = cli_factory.cli_options()?.start_dir.dir_path(); + + // only prefer to add npm deps to `package.json` if there isn't a closer deno.json. + // example: if deno.json is in the CWD and package.json is in the parent, we should add + // npm deps to deno.json, since it's closer + let prefer_npm_config = match (npm_config.as_ref(), deno_config.as_ref()) { + (Some(npm), Some(deno)) => { + let npm_distance = path_distance(&npm.path, &start_dir); + let deno_distance = path_distance(&deno.path, &start_dir); + npm_distance <= deno_distance + } + (Some(_), None) => true, + (None, _) => false, + }; + let http_client = cli_factory.http_client_provider(); let deps_http_cache = cli_factory.global_http_cache()?; let mut deps_file_fetcher = FileFetcher::new( @@ -455,7 +479,7 @@ pub async fn add( selected_package.selected_version ); - if selected_package.package_name.starts_with("npm:") { + if selected_package.package_name.starts_with("npm:") && prefer_npm_config { if let Some(npm) = &mut npm_config { npm.add(selected_package, dev); } else { -- cgit v1.2.3 From fe9f0ee5934871175758857899fe64e56c397fd5 Mon Sep 17 00:00:00 2001 From: Leo Kettmeir Date: Mon, 4 Nov 2024 09:17:21 -0800 Subject: refactor(runtime/permissions): use concrete error types (#26464) --- cli/tools/info.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'cli/tools') diff --git a/cli/tools/info.rs b/cli/tools/info.rs index b53485bd6..c2f5a8cb8 100644 --- a/cli/tools/info.rs +++ b/cli/tools/info.rs @@ -645,10 +645,12 @@ impl<'a> GraphDisplayContext<'a> { let message = match err { HttpsChecksumIntegrity(_) => "(checksum integrity error)", Decode(_) => "(loading decode error)", - Loader(err) => match deno_core::error::get_custom_error_class(err) { - Some("NotCapable") => "(not capable, requires --allow-import)", - _ => "(loading error)", - }, + Loader(err) => { + match deno_runtime::errors::get_error_class_name(err) { + Some("NotCapable") => "(not capable, requires --allow-import)", + _ => "(loading error)", + } + } Jsr(_) => "(loading error)", NodeUnknownBuiltinModule(_) => "(unknown node built-in error)", Npm(_) => "(npm loading error)", -- cgit v1.2.3 From 9a39a98b57de4183e054ab170592c85c97fac183 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Mon, 4 Nov 2024 17:57:29 +0000 Subject: fix(fmt): ignore file directive for YAML files (#26717) Closes https://github.com/denoland/deno/issues/26712 Support `# deno-fmt-ignore-file` directive for YAML files. Also added tests for single line ignores. --- cli/tools/fmt.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'cli/tools') diff --git a/cli/tools/fmt.rs b/cli/tools/fmt.rs index 81af25c34..837883561 100644 --- a/cli/tools/fmt.rs +++ b/cli/tools/fmt.rs @@ -353,6 +353,21 @@ fn format_yaml( file_text: &str, fmt_options: &FmtOptionsConfig, ) -> Result, AnyError> { + let ignore_file = file_text + .lines() + .take_while(|line| line.starts_with('#')) + .any(|line| { + line + .strip_prefix('#') + .unwrap() + .trim() + .starts_with("deno-fmt-ignore-file") + }); + + if ignore_file { + return Ok(None); + } + let formatted_str = pretty_yaml::format_text(file_text, &get_resolved_yaml_config(fmt_options)) .map_err(AnyError::from)?; -- cgit v1.2.3 From 706b1dfcea8ab6bf7d155893ab795669107516a8 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker <17734409+nathanwhit@users.noreply.github.com> Date: Mon, 4 Nov 2024 18:45:00 -0800 Subject: fix(add): better error message when adding package that only has pre-release versions (#26724) Fixes https://github.com/denoland/deno/issues/26597 A small refactor as well to reduce some code duplication --- cli/tools/registry/pm.rs | 205 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 146 insertions(+), 59 deletions(-) (limited to 'cli/tools') diff --git a/cli/tools/registry/pm.rs b/cli/tools/registry/pm.rs index 4e0387998..68913e259 100644 --- a/cli/tools/registry/pm.rs +++ b/cli/tools/registry/pm.rs @@ -12,7 +12,9 @@ use deno_core::futures::StreamExt; use deno_path_util::url_to_file_path; use deno_semver::jsr::JsrPackageReqReference; use deno_semver::npm::NpmPackageReqReference; +use deno_semver::package::PackageNv; use deno_semver::package::PackageReq; +use deno_semver::Version; use deno_semver::VersionReq; use jsonc_parser::cst::CstObject; use jsonc_parser::cst::CstObjectProp; @@ -455,15 +457,32 @@ pub async fn add( match package_and_version { PackageAndVersion::NotFound { package: package_name, - found_npm_package, + help, package_req, - } => { - if found_npm_package { - bail!("{} was not found, but a matching npm package exists. Did you mean `{}`?", crate::colors::red(package_name), crate::colors::yellow(format!("deno {cmd_name} npm:{package_req}"))); - } else { - bail!("{} was not found.", crate::colors::red(package_name)); + } => match help { + Some(NotFoundHelp::NpmPackage) => { + bail!( + "{} was not found, but a matching npm package exists. Did you mean `{}`?", + crate::colors::red(package_name), + crate::colors::yellow(format!("deno {cmd_name} npm:{package_req}")) + ); } - } + Some(NotFoundHelp::JsrPackage) => { + bail!( + "{} was not found, but a matching jsr package exists. Did you mean `{}`?", + crate::colors::red(package_name), + crate::colors::yellow(format!("deno {cmd_name} jsr:{package_req}")) + ) + } + Some(NotFoundHelp::PreReleaseVersion(version)) => { + bail!( + "{} has only pre-release versions available. Try specifying a version: `{}`", + crate::colors::red(&package_name), + crate::colors::yellow(format!("deno {cmd_name} {package_name}@^{version}")) + ) + } + None => bail!("{} was not found.", crate::colors::red(package_name)), + }, PackageAndVersion::Selected(selected) => { selected_packages.push(selected); } @@ -511,76 +530,144 @@ struct SelectedPackage { selected_version: String, } +enum NotFoundHelp { + NpmPackage, + JsrPackage, + PreReleaseVersion(Version), +} + enum PackageAndVersion { NotFound { package: String, - found_npm_package: bool, package_req: PackageReq, + help: Option, }, Selected(SelectedPackage), } +fn best_version<'a>( + versions: impl Iterator, +) -> Option<&'a Version> { + let mut maybe_best_version: Option<&Version> = None; + for version in versions { + let is_best_version = maybe_best_version + .as_ref() + .map(|best_version| (*best_version).cmp(version).is_lt()) + .unwrap_or(true); + if is_best_version { + maybe_best_version = Some(version); + } + } + maybe_best_version +} + +trait PackageInfoProvider { + const SPECIFIER_PREFIX: &str; + /// The help to return if a package is found by this provider + const HELP: NotFoundHelp; + async fn req_to_nv(&self, req: &PackageReq) -> Option; + async fn latest_version<'a>(&self, req: &PackageReq) -> Option; +} + +impl PackageInfoProvider for Arc { + const HELP: NotFoundHelp = NotFoundHelp::JsrPackage; + const SPECIFIER_PREFIX: &str = "jsr"; + async fn req_to_nv(&self, req: &PackageReq) -> Option { + (**self).req_to_nv(req).await + } + + async fn latest_version<'a>(&self, req: &PackageReq) -> Option { + let info = self.package_info(&req.name).await?; + best_version( + info + .versions + .iter() + .filter(|(_, version_info)| !version_info.yanked) + .map(|(version, _)| version), + ) + .cloned() + } +} + +impl PackageInfoProvider for Arc { + const HELP: NotFoundHelp = NotFoundHelp::NpmPackage; + const SPECIFIER_PREFIX: &str = "npm"; + async fn req_to_nv(&self, req: &PackageReq) -> Option { + (**self).req_to_nv(req).await + } + + async fn latest_version<'a>(&self, req: &PackageReq) -> Option { + let info = self.package_info(&req.name).await?; + best_version(info.versions.keys()).cloned() + } +} + async fn find_package_and_select_version_for_req( jsr_resolver: Arc, npm_resolver: Arc, add_package_req: AddRmPackageReq, ) -> Result { - match add_package_req.value { - AddRmPackageReqValue::Jsr(req) => { - let jsr_prefixed_name = format!("jsr:{}", &req.name); - let Some(nv) = jsr_resolver.req_to_nv(&req).await else { - if npm_resolver.req_to_nv(&req).await.is_some() { + async fn select( + main_resolver: T, + fallback_resolver: S, + add_package_req: AddRmPackageReq, + ) -> Result { + let req = match &add_package_req.value { + AddRmPackageReqValue::Jsr(req) => req, + AddRmPackageReqValue::Npm(req) => req, + }; + let prefixed_name = format!("{}:{}", T::SPECIFIER_PREFIX, req.name); + let help_if_found_in_fallback = S::HELP; + let Some(nv) = main_resolver.req_to_nv(req).await else { + if fallback_resolver.req_to_nv(req).await.is_some() { + // it's in the other registry + return Ok(PackageAndVersion::NotFound { + package: prefixed_name, + help: Some(help_if_found_in_fallback), + package_req: req.clone(), + }); + } + if req.version_req.version_text() == "*" { + if let Some(pre_release_version) = + main_resolver.latest_version(req).await + { return Ok(PackageAndVersion::NotFound { - package: jsr_prefixed_name, - found_npm_package: true, - package_req: req, + package: prefixed_name, + package_req: req.clone(), + help: Some(NotFoundHelp::PreReleaseVersion( + pre_release_version.clone(), + )), }); } + } - return Ok(PackageAndVersion::NotFound { - package: jsr_prefixed_name, - found_npm_package: false, - package_req: req, - }); - }; - let range_symbol = if req.version_req.version_text().starts_with('~') { - "~" - } else if req.version_req.version_text() == nv.version.to_string() { - "" - } else { - "^" - }; - Ok(PackageAndVersion::Selected(SelectedPackage { - import_name: add_package_req.alias, - package_name: jsr_prefixed_name, - version_req: format!("{}{}", range_symbol, &nv.version), - selected_version: nv.version.to_string(), - })) - } - AddRmPackageReqValue::Npm(req) => { - let npm_prefixed_name = format!("npm:{}", &req.name); - let Some(nv) = npm_resolver.req_to_nv(&req).await else { - return Ok(PackageAndVersion::NotFound { - package: npm_prefixed_name, - found_npm_package: false, - package_req: req, - }); - }; + return Ok(PackageAndVersion::NotFound { + package: prefixed_name, + help: None, + package_req: req.clone(), + }); + }; + let range_symbol = if req.version_req.version_text().starts_with('~') { + "~" + } else if req.version_req.version_text() == nv.version.to_string() { + "" + } else { + "^" + }; + Ok(PackageAndVersion::Selected(SelectedPackage { + import_name: add_package_req.alias, + package_name: prefixed_name, + version_req: format!("{}{}", range_symbol, &nv.version), + selected_version: nv.version.to_string(), + })) + } - let range_symbol = if req.version_req.version_text().starts_with('~') { - "~" - } else if req.version_req.version_text() == nv.version.to_string() { - "" - } else { - "^" - }; - - Ok(PackageAndVersion::Selected(SelectedPackage { - import_name: add_package_req.alias, - package_name: npm_prefixed_name, - version_req: format!("{}{}", range_symbol, &nv.version), - selected_version: nv.version.to_string(), - })) + match &add_package_req.value { + AddRmPackageReqValue::Jsr(_) => { + select(jsr_resolver, npm_resolver, add_package_req).await + } + AddRmPackageReqValue::Npm(_) => { + select(npm_resolver, jsr_resolver, add_package_req).await } } } -- cgit v1.2.3 From 64e887083aa67047f5ad37b9d55c418274b03ea3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 6 Nov 2024 12:56:03 +0000 Subject: fix(fmt): don't use self-closing tags in HTML (#26754) Closes https://github.com/denoland/deno/issues/26748 --- cli/tools/fmt.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'cli/tools') diff --git a/cli/tools/fmt.rs b/cli/tools/fmt.rs index 837883561..f7f8dabc6 100644 --- a/cli/tools/fmt.rs +++ b/cli/tools/fmt.rs @@ -1032,7 +1032,7 @@ fn get_resolved_markup_fmt_config( max_attrs_per_line: None, prefer_attrs_single_line: false, html_normal_self_closing: None, - html_void_self_closing: Some(true), + html_void_self_closing: None, component_self_closing: None, svg_self_closing: None, mathml_self_closing: None, -- cgit v1.2.3 From d4f1bd3dacf54c4625eef7828341b39286ead8cb Mon Sep 17 00:00:00 2001 From: Nathan Whitaker <17734409+nathanwhit@users.noreply.github.com> Date: Fri, 8 Nov 2024 12:45:30 -0800 Subject: fix(install): cache jsr deps from all workspace config files (#26779) Fixes #26772. I wasn't aware that the `imports()` method only returned the workspace root imports --- cli/tools/registry/pm/cache_deps.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'cli/tools') diff --git a/cli/tools/registry/pm/cache_deps.rs b/cli/tools/registry/pm/cache_deps.rs index 365622d11..d3c8da868 100644 --- a/cli/tools/registry/pm/cache_deps.rs +++ b/cli/tools/registry/pm/cache_deps.rs @@ -44,7 +44,11 @@ pub async fn cache_top_level_deps( let mut seen_reqs = std::collections::HashSet::new(); - for entry in import_map.imports().entries() { + for entry in import_map.imports().entries().chain( + import_map + .scopes() + .flat_map(|scope| scope.imports.entries()), + ) { let Some(specifier) = entry.value else { continue; }; -- cgit v1.2.3 From 01de3317424cc870913dbe85ff3b80eadaf8cc87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sat, 9 Nov 2024 15:19:46 +0000 Subject: perf(upgrade): cache downloaded binaries in DENO_DIR (#26108) Co-authored-by: Divy Srivastava --- cli/tools/upgrade.rs | 69 +++++++++++----------------------------------------- 1 file changed, 14 insertions(+), 55 deletions(-) (limited to 'cli/tools') diff --git a/cli/tools/upgrade.rs b/cli/tools/upgrade.rs index 77a9f72b8..ed1bc06cf 100644 --- a/cli/tools/upgrade.rs +++ b/cli/tools/upgrade.rs @@ -6,13 +6,14 @@ use crate::args::Flags; use crate::args::UpgradeFlags; use crate::args::UPGRADE_USAGE; use crate::colors; +use crate::download_deno_binary::archive_name; +use crate::download_deno_binary::download_deno_binary; +use crate::download_deno_binary::BinaryKind; use crate::factory::CliFactory; use crate::http_util::HttpClient; use crate::http_util::HttpClientProvider; use crate::shared::ReleaseChannel; use crate::util::archive; -use crate::util::progress_bar::ProgressBar; -use crate::util::progress_bar::ProgressBarStyle; use crate::version; use async_trait::async_trait; @@ -34,12 +35,8 @@ use std::process::Command; use std::sync::Arc; use std::time::Duration; -const RELEASE_URL: &str = "https://github.com/denoland/deno/releases"; -const CANARY_URL: &str = "https://dl.deno.land/canary"; -const DL_RELEASE_URL: &str = "https://dl.deno.land/release"; - -pub static ARCHIVE_NAME: Lazy = - Lazy::new(|| format!("deno-{}.zip", env!("TARGET"))); +static ARCHIVE_NAME: Lazy = + Lazy::new(|| archive_name(BinaryKind::Deno, env!("TARGET"))); // How often query server for new version. In hours. const UPGRADE_CHECK_INTERVAL: i64 = 24; @@ -532,13 +529,17 @@ pub async fn upgrade( return Ok(()); }; - let download_url = get_download_url( + let binary_path = download_deno_binary( + http_client_provider, + factory.deno_dir()?, + BinaryKind::Deno, + env!("TARGET"), &selected_version_to_upgrade.version_or_hash, requested_version.release_channel(), - )?; - log::info!("{}", colors::gray(format!("Downloading {}", &download_url))); - let Some(archive_data) = download_package(&client, download_url).await? - else { + ) + .await?; + + let Ok(archive_data) = tokio::fs::read(&binary_path).await else { log::error!("Download could not be found, aborting"); std::process::exit(1) }; @@ -881,48 +882,6 @@ fn base_upgrade_url() -> Cow<'static, str> { } } -fn get_download_url( - version: &str, - release_channel: ReleaseChannel, -) -> Result { - let download_url = match release_channel { - ReleaseChannel::Stable => { - format!("{}/download/v{}/{}", RELEASE_URL, version, *ARCHIVE_NAME) - } - ReleaseChannel::Rc => { - format!("{}/v{}/{}", DL_RELEASE_URL, version, *ARCHIVE_NAME) - } - ReleaseChannel::Canary => { - format!("{}/{}/{}", CANARY_URL, version, *ARCHIVE_NAME) - } - ReleaseChannel::Lts => { - format!("{}/v{}/{}", DL_RELEASE_URL, version, *ARCHIVE_NAME) - } - }; - - Url::parse(&download_url).with_context(|| { - format!( - "Failed to parse URL to download new release: {}", - download_url - ) - }) -} - -async fn download_package( - client: &HttpClient, - download_url: Url, -) -> Result>, AnyError> { - let progress_bar = ProgressBar::new(ProgressBarStyle::DownloadBars); - // provide an empty string here in order to prefer the downloading - // text above which will stay alive after the progress bars are complete - let progress = progress_bar.update(""); - let maybe_bytes = client - .download_with_progress_and_retries(download_url.clone(), None, &progress) - .await - .with_context(|| format!("Failed downloading {download_url}. The version you requested may not have been built for the current architecture."))?; - Ok(maybe_bytes) -} - fn replace_exe(from: &Path, to: &Path) -> Result<(), std::io::Error> { if cfg!(windows) { // On windows you cannot replace the currently running executable. -- cgit v1.2.3 From ce778a947e70616b435920fd6f1fc28c4b2a78d1 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Sun, 10 Nov 2024 09:00:44 +0530 Subject: Revert "perf(upgrade): cache downloaded binaries in DENO_DIR" (#26799) Reverts denoland/deno#26108 Tests are flaky on main https://github.com/denoland/deno/commit/01de3317424cc870913dbe85ff3b80eadaf8cc87 --- cli/tools/upgrade.rs | 69 +++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 55 insertions(+), 14 deletions(-) (limited to 'cli/tools') diff --git a/cli/tools/upgrade.rs b/cli/tools/upgrade.rs index ed1bc06cf..77a9f72b8 100644 --- a/cli/tools/upgrade.rs +++ b/cli/tools/upgrade.rs @@ -6,14 +6,13 @@ use crate::args::Flags; use crate::args::UpgradeFlags; use crate::args::UPGRADE_USAGE; use crate::colors; -use crate::download_deno_binary::archive_name; -use crate::download_deno_binary::download_deno_binary; -use crate::download_deno_binary::BinaryKind; use crate::factory::CliFactory; use crate::http_util::HttpClient; use crate::http_util::HttpClientProvider; use crate::shared::ReleaseChannel; use crate::util::archive; +use crate::util::progress_bar::ProgressBar; +use crate::util::progress_bar::ProgressBarStyle; use crate::version; use async_trait::async_trait; @@ -35,8 +34,12 @@ use std::process::Command; use std::sync::Arc; use std::time::Duration; -static ARCHIVE_NAME: Lazy = - Lazy::new(|| archive_name(BinaryKind::Deno, env!("TARGET"))); +const RELEASE_URL: &str = "https://github.com/denoland/deno/releases"; +const CANARY_URL: &str = "https://dl.deno.land/canary"; +const DL_RELEASE_URL: &str = "https://dl.deno.land/release"; + +pub static ARCHIVE_NAME: Lazy = + Lazy::new(|| format!("deno-{}.zip", env!("TARGET"))); // How often query server for new version. In hours. const UPGRADE_CHECK_INTERVAL: i64 = 24; @@ -529,17 +532,13 @@ pub async fn upgrade( return Ok(()); }; - let binary_path = download_deno_binary( - http_client_provider, - factory.deno_dir()?, - BinaryKind::Deno, - env!("TARGET"), + let download_url = get_download_url( &selected_version_to_upgrade.version_or_hash, requested_version.release_channel(), - ) - .await?; - - let Ok(archive_data) = tokio::fs::read(&binary_path).await else { + )?; + log::info!("{}", colors::gray(format!("Downloading {}", &download_url))); + let Some(archive_data) = download_package(&client, download_url).await? + else { log::error!("Download could not be found, aborting"); std::process::exit(1) }; @@ -882,6 +881,48 @@ fn base_upgrade_url() -> Cow<'static, str> { } } +fn get_download_url( + version: &str, + release_channel: ReleaseChannel, +) -> Result { + let download_url = match release_channel { + ReleaseChannel::Stable => { + format!("{}/download/v{}/{}", RELEASE_URL, version, *ARCHIVE_NAME) + } + ReleaseChannel::Rc => { + format!("{}/v{}/{}", DL_RELEASE_URL, version, *ARCHIVE_NAME) + } + ReleaseChannel::Canary => { + format!("{}/{}/{}", CANARY_URL, version, *ARCHIVE_NAME) + } + ReleaseChannel::Lts => { + format!("{}/v{}/{}", DL_RELEASE_URL, version, *ARCHIVE_NAME) + } + }; + + Url::parse(&download_url).with_context(|| { + format!( + "Failed to parse URL to download new release: {}", + download_url + ) + }) +} + +async fn download_package( + client: &HttpClient, + download_url: Url, +) -> Result>, AnyError> { + let progress_bar = ProgressBar::new(ProgressBarStyle::DownloadBars); + // provide an empty string here in order to prefer the downloading + // text above which will stay alive after the progress bars are complete + let progress = progress_bar.update(""); + let maybe_bytes = client + .download_with_progress_and_retries(download_url.clone(), None, &progress) + .await + .with_context(|| format!("Failed downloading {download_url}. The version you requested may not have been built for the current architecture."))?; + Ok(maybe_bytes) +} + fn replace_exe(from: &Path, to: &Path) -> Result<(), std::io::Error> { if cfg!(windows) { // On windows you cannot replace the currently running executable. -- cgit v1.2.3 From f091d1ad69b4e5217ae3272b641171781a372c4f Mon Sep 17 00:00:00 2001 From: David Sherret Date: Wed, 13 Nov 2024 10:10:09 -0500 Subject: feat(node): stabilize detecting if CJS via `"type": "commonjs"` in a package.json (#26439) This will respect `"type": "commonjs"` in a package.json to determine if `.js`/`.jsx`/`.ts`/.tsx` files are CJS or ESM. If the file is found to be ESM it will be loaded as ESM though. --- cli/tools/coverage/mod.rs | 2 +- cli/tools/installer.rs | 1 + cli/tools/jupyter/mod.rs | 2 +- cli/tools/lint/mod.rs | 2 +- cli/tools/lint/rules/no_sloppy_imports.rs | 1 + cli/tools/repl/session.rs | 17 +++++++++++------ cli/tools/run/hmr.rs | 7 ------- 7 files changed, 16 insertions(+), 16 deletions(-) (limited to 'cli/tools') diff --git a/cli/tools/coverage/mod.rs b/cli/tools/coverage/mod.rs index f59333247..2a554c133 100644 --- a/cli/tools/coverage/mod.rs +++ b/cli/tools/coverage/mod.rs @@ -480,7 +480,7 @@ fn filter_coverages( .filter(|e| { let is_internal = e.url.starts_with("ext:") || e.url.ends_with("__anonymous__") - || e.url.ends_with("$deno$test.js") + || e.url.ends_with("$deno$test.mjs") || e.url.ends_with(".snap") || is_supported_test_path(Path::new(e.url.as_str())) || doc_test_re.is_match(e.url.as_str()) diff --git a/cli/tools/installer.rs b/cli/tools/installer.rs index ed86e86c7..1655f0a32 100644 --- a/cli/tools/installer.rs +++ b/cli/tools/installer.rs @@ -1396,6 +1396,7 @@ mod tests { .env_clear() // use the deno binary in the target directory .env("PATH", test_util::target_dir()) + .env("RUST_BACKTRACE", "1") .spawn() .unwrap() .wait() diff --git a/cli/tools/jupyter/mod.rs b/cli/tools/jupyter/mod.rs index 0ffd0da1e..732f95c49 100644 --- a/cli/tools/jupyter/mod.rs +++ b/cli/tools/jupyter/mod.rs @@ -61,7 +61,7 @@ pub async fn kernel( let factory = CliFactory::from_flags(flags); let cli_options = factory.cli_options()?; let main_module = - resolve_url_or_path("./$deno$jupyter.ts", cli_options.initial_cwd()) + resolve_url_or_path("./$deno$jupyter.mts", cli_options.initial_cwd()) .unwrap(); // TODO(bartlomieju): should we run with all permissions? let permissions = diff --git a/cli/tools/lint/mod.rs b/cli/tools/lint/mod.rs index e096b486e..d8edf2404 100644 --- a/cli/tools/lint/mod.rs +++ b/cli/tools/lint/mod.rs @@ -63,7 +63,7 @@ pub use rules::LintRuleProvider; const JSON_SCHEMA_VERSION: u8 = 1; -static STDIN_FILE_NAME: &str = "$deno$stdin.ts"; +static STDIN_FILE_NAME: &str = "$deno$stdin.mts"; pub async fn lint( flags: Arc, diff --git a/cli/tools/lint/rules/no_sloppy_imports.rs b/cli/tools/lint/rules/no_sloppy_imports.rs index 2f6087588..94bf9a7c6 100644 --- a/cli/tools/lint/rules/no_sloppy_imports.rs +++ b/cli/tools/lint/rules/no_sloppy_imports.rs @@ -87,6 +87,7 @@ impl LintRule for NoSloppyImportsRule { captures: Default::default(), }; + // fill this and capture the sloppy imports in the resolver deno_graph::parse_module_from_ast(deno_graph::ParseModuleFromAstOptions { graph_kind: deno_graph::GraphKind::All, specifier: context.specifier().clone(), diff --git a/cli/tools/repl/session.rs b/cli/tools/repl/session.rs index 23b0f11ac..8e05c4abb 100644 --- a/cli/tools/repl/session.rs +++ b/cli/tools/repl/session.rs @@ -7,7 +7,7 @@ use crate::cdp; use crate::colors; use crate::lsp::ReplLanguageServer; use crate::npm::CliNpmResolver; -use crate::resolver::CliGraphResolver; +use crate::resolver::CliResolver; use crate::tools::test::report_tests; use crate::tools::test::reporters::PrettyTestReporter; use crate::tools::test::reporters::TestReporter; @@ -44,12 +44,12 @@ use deno_core::url::Url; use deno_core::LocalInspectorSession; use deno_core::PollEventLoopOptions; use deno_graph::source::ResolutionMode; -use deno_graph::source::Resolver; use deno_graph::Position; use deno_graph::PositionRange; use deno_graph::SpecifierWithRange; use deno_runtime::worker::MainWorker; use deno_semver::npm::NpmPackageReqReference; +use node_resolver::NodeModuleKind; use once_cell::sync::Lazy; use regex::Match; use regex::Regex; @@ -180,7 +180,7 @@ struct ReplJsxState { pub struct ReplSession { npm_resolver: Arc, - resolver: Arc, + resolver: Arc, pub worker: MainWorker, session: LocalInspectorSession, pub context_id: u64, @@ -199,7 +199,7 @@ impl ReplSession { pub async fn initialize( cli_options: &CliOptions, npm_resolver: Arc, - resolver: Arc, + resolver: Arc, mut worker: MainWorker, main_module: ModuleSpecifier, test_event_receiver: TestEventReceiver, @@ -245,7 +245,7 @@ impl ReplSession { assert_ne!(context_id, 0); let referrer = - deno_core::resolve_path("./$deno$repl.ts", cli_options.initial_cwd()) + deno_core::resolve_path("./$deno$repl.mts", cli_options.initial_cwd()) .unwrap(); let cwd_url = @@ -712,7 +712,12 @@ impl ReplSession { .flat_map(|i| { self .resolver - .resolve(i, &referrer_range, ResolutionMode::Execution) + .resolve( + i, + &referrer_range, + NodeModuleKind::Esm, + ResolutionMode::Execution, + ) .ok() .or_else(|| ModuleSpecifier::parse(i).ok()) }) diff --git a/cli/tools/run/hmr.rs b/cli/tools/run/hmr.rs index 6cebedd01..373c207d6 100644 --- a/cli/tools/run/hmr.rs +++ b/cli/tools/run/hmr.rs @@ -4,8 +4,6 @@ use std::collections::HashMap; use std::path::PathBuf; use std::sync::Arc; -use deno_ast::MediaType; -use deno_ast::ModuleKind; use deno_core::error::generic_error; use deno_core::error::AnyError; use deno_core::futures::StreamExt; @@ -18,7 +16,6 @@ use tokio::select; use crate::cdp; use crate::emit::Emitter; -use crate::resolver::CjsTracker; use crate::util::file_watcher::WatcherCommunicator; use crate::util::file_watcher::WatcherRestartMode; @@ -63,7 +60,6 @@ pub struct HmrRunner { session: LocalInspectorSession, watcher_communicator: Arc, script_ids: HashMap, - cjs_tracker: Arc, emitter: Arc, } @@ -146,7 +142,6 @@ impl crate::worker::HmrRunner for HmrRunner { let source_code = self.emitter.load_and_emit_for_hmr( &module_url, - ModuleKind::from_is_cjs(self.cjs_tracker.is_maybe_cjs(&module_url, MediaType::from_specifier(&module_url))?), ).await?; let mut tries = 1; @@ -179,14 +174,12 @@ impl crate::worker::HmrRunner for HmrRunner { impl HmrRunner { pub fn new( - cjs_tracker: Arc, emitter: Arc, session: LocalInspectorSession, watcher_communicator: Arc, ) -> Self { Self { session, - cjs_tracker, emitter, watcher_communicator, script_ids: HashMap::new(), -- cgit v1.2.3 From 6b5cb41545086a7a550c698620f5b7bb19b5524f Mon Sep 17 00:00:00 2001 From: David Sherret Date: Wed, 13 Nov 2024 10:39:40 -0500 Subject: fix(fmt): error instead of panic on unstable format (#26859) --- cli/tools/fmt.rs | 48 ++++++++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 20 deletions(-) (limited to 'cli/tools') diff --git a/cli/tools/fmt.rs b/cli/tools/fmt.rs index f7f8dabc6..56b1632cf 100644 --- a/cli/tools/fmt.rs +++ b/cli/tools/fmt.rs @@ -790,28 +790,26 @@ fn format_ensure_stable( return Ok(Some(current_text)); } Err(err) => { - panic!( + bail!( concat!( "Formatting succeeded initially, but failed when ensuring a ", "stable format. This indicates a bug in the formatter where ", "the text it produces is not syntactically correct. As a temporary ", - "workaround you can ignore this file ({}).\n\n{:#}" + "workaround you can ignore this file.\n\n{:#}" ), - file_path.display(), err, ) } } count += 1; if count == 5 { - panic!( + bail!( concat!( "Formatting not stable. Bailed after {} tries. This indicates a bug ", - "in the formatter where it formats the file ({}) differently each time. As a ", + "in the formatter where it formats the file differently each time. As a ", "temporary workaround you can ignore this file." ), count, - file_path.display(), ) } } @@ -1215,6 +1213,8 @@ fn is_supported_ext_fmt(path: &Path) -> bool { #[cfg(test)] mod test { + use test_util::assert_starts_with; + use super::*; #[test] @@ -1270,12 +1270,16 @@ mod test { } #[test] - #[should_panic(expected = "Formatting not stable. Bailed after 5 tries.")] fn test_format_ensure_stable_unstable_format() { - format_ensure_stable(&PathBuf::from("mod.ts"), "1", |_, file_text| { - Ok(Some(format!("1{file_text}"))) - }) - .unwrap(); + let err = + format_ensure_stable(&PathBuf::from("mod.ts"), "1", |_, file_text| { + Ok(Some(format!("1{file_text}"))) + }) + .unwrap_err(); + assert_starts_with!( + err.to_string(), + "Formatting not stable. Bailed after 5 tries." + ); } #[test] @@ -1289,16 +1293,20 @@ mod test { } #[test] - #[should_panic(expected = "Formatting succeeded initially, but failed when")] fn test_format_ensure_stable_error_second() { - format_ensure_stable(&PathBuf::from("mod.ts"), "1", |_, file_text| { - if file_text == "1" { - Ok(Some("11".to_string())) - } else { - bail!("Error formatting.") - } - }) - .unwrap(); + let err = + format_ensure_stable(&PathBuf::from("mod.ts"), "1", |_, file_text| { + if file_text == "1" { + Ok(Some("11".to_string())) + } else { + bail!("Error formatting.") + } + }) + .unwrap_err(); + assert_starts_with!( + err.to_string(), + "Formatting succeeded initially, but failed when" + ); } #[test] -- cgit v1.2.3 From 4e899d48cffa95617266dd8f9aef54603a87ad82 Mon Sep 17 00:00:00 2001 From: snek Date: Thu, 14 Nov 2024 13:16:28 +0100 Subject: fix: otel resiliency (#26857) Improving the breadth of collected data, and ensuring that the collected data is more likely to be successfully reported. - Use `log` crate in more places - Hook up `log` crate to otel - Switch to process-wide otel processors - Handle places that use `process::exit` Also adds a more robust testing framework, with a deterministic tracing setting. Refs: https://github.com/denoland/deno/issues/26852 --- cli/tools/lint/mod.rs | 2 +- cli/tools/test/mod.rs | 2 ++ cli/tools/upgrade.rs | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) (limited to 'cli/tools') diff --git a/cli/tools/lint/mod.rs b/cli/tools/lint/mod.rs index d8edf2404..36f1cc049 100644 --- a/cli/tools/lint/mod.rs +++ b/cli/tools/lint/mod.rs @@ -191,7 +191,7 @@ pub async fn lint( linter.finish() }; if !success { - std::process::exit(1); + deno_runtime::exit(1); } } diff --git a/cli/tools/test/mod.rs b/cli/tools/test/mod.rs index fa849614f..966b0d285 100644 --- a/cli/tools/test/mod.rs +++ b/cli/tools/test/mod.rs @@ -1357,6 +1357,7 @@ pub async fn report_tests( if let Err(err) = reporter.flush_report(&elapsed, &tests, &test_steps) { eprint!("Test reporter failed to flush: {}", err) } + #[allow(clippy::disallowed_methods)] std::process::exit(130); } } @@ -1642,6 +1643,7 @@ pub async fn run_tests_with_watch( loop { signal::ctrl_c().await.unwrap(); if !HAS_TEST_RUN_SIGINT_HANDLER.load(Ordering::Relaxed) { + #[allow(clippy::disallowed_methods)] std::process::exit(130); } } diff --git a/cli/tools/upgrade.rs b/cli/tools/upgrade.rs index 77a9f72b8..cb85859f7 100644 --- a/cli/tools/upgrade.rs +++ b/cli/tools/upgrade.rs @@ -540,7 +540,7 @@ pub async fn upgrade( let Some(archive_data) = download_package(&client, download_url).await? else { log::error!("Download could not be found, aborting"); - std::process::exit(1) + deno_runtime::exit(1) }; log::info!( -- cgit v1.2.3 From 84e12386480d76e97ad85d9c5314168e7ab03e04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sat, 16 Nov 2024 11:18:17 +0000 Subject: feat(task): support object notation, remove support for JSDocs (#26886) This commit changes three aspects of `deno task`: 1. Tasks can now be written using object notation like so: ```jsonc { "tasks": { "foo": "deno run foo.js", "bar": { "command": "deno run bar.js" } } ``` 2. Support for comments for tasks is now removed. Comments above tasks will no longer be printed when running `deno task`. 3. Tasks written using object notation can have "description" field that replaces support for comments above tasks: ```jsonc { "tasks": { "bar": { "description": "This is a bar task" "command": "deno run bar.js" } } ``` ``` $ deno task Available tasks: - bar // This is a bar task deno run bar.js ``` Pulled most of the changes from https://github.com/denoland/deno/pull/26467 to support "dependencies" in tasks. Additionally some cleanup was performed to make code easier to read. --------- Co-authored-by: David Sherret --- cli/tools/task.rs | 428 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 236 insertions(+), 192 deletions(-) (limited to 'cli/tools') diff --git a/cli/tools/task.rs b/cli/tools/task.rs index 502b09d2c..a13efbaf4 100644 --- a/cli/tools/task.rs +++ b/cli/tools/task.rs @@ -1,6 +1,5 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use std::borrow::Cow; use std::collections::HashMap; use std::collections::HashSet; use std::path::Path; @@ -8,7 +7,7 @@ use std::path::PathBuf; use std::rc::Rc; use std::sync::Arc; -use deno_config::deno_json::Task; +use deno_config::workspace::TaskDefinition; use deno_config::workspace::TaskOrScript; use deno_config::workspace::WorkspaceDirectory; use deno_config::workspace::WorkspaceTasksConfig; @@ -16,8 +15,11 @@ use deno_core::anyhow::anyhow; use deno_core::anyhow::bail; use deno_core::anyhow::Context; use deno_core::error::AnyError; +use deno_core::url::Url; use deno_path_util::normalize_path; +use deno_runtime::deno_node::NodeResolver; use deno_task_shell::ShellCommand; +use indexmap::IndexMap; use crate::args::CliOptions; use crate::args::Flags; @@ -48,155 +50,188 @@ pub async fn execute_script( v == "1" }) .unwrap_or(false); - let tasks_config = start_dir.to_tasks_config()?; - let tasks_config = if force_use_pkg_json { - tasks_config.with_only_pkg_json() - } else { - tasks_config - }; + let mut tasks_config = start_dir.to_tasks_config()?; + if force_use_pkg_json { + tasks_config = tasks_config.with_only_pkg_json() + } - let task_name = match &task_flags.task { - Some(task) => task, - None => { - print_available_tasks( - &mut std::io::stdout(), - &cli_options.start_dir, - &tasks_config, - )?; - return Ok(0); - } + let Some(task_name) = &task_flags.task else { + print_available_tasks( + &mut std::io::stdout(), + &cli_options.start_dir, + &tasks_config, + )?; + return Ok(0); }; let npm_resolver = factory.npm_resolver().await?; let node_resolver = factory.node_resolver().await?; let env_vars = task_runner::real_env_vars(); - match tasks_config.task(task_name) { - Some((dir_url, task_or_script)) => match task_or_script { - TaskOrScript::Task(_tasks, script) => { - let cwd = match task_flags.cwd { - Some(path) => canonicalize_path(&PathBuf::from(path)) - .context("failed canonicalizing --cwd")?, - None => normalize_path(dir_url.to_file_path().unwrap()), - }; - - let custom_commands = task_runner::resolve_custom_commands( - npm_resolver.as_ref(), - node_resolver, - )?; - run_task(RunTaskOptions { - task_name, - script, - cwd: &cwd, - env_vars, - custom_commands, - npm_resolver: npm_resolver.as_ref(), - cli_options, - }) - .await - } - TaskOrScript::Script(scripts, _script) => { - // ensure the npm packages are installed if using a managed resolver - if let Some(npm_resolver) = npm_resolver.as_managed() { - npm_resolver.ensure_top_level_package_json_install().await?; - } + let task_runner = TaskRunner { + tasks_config, + task_flags: &task_flags, + npm_resolver: npm_resolver.as_ref(), + node_resolver: node_resolver.as_ref(), + env_vars, + cli_options, + }; + task_runner.run_task(task_name).await +} - let cwd = match task_flags.cwd { - Some(path) => canonicalize_path(&PathBuf::from(path))?, - None => normalize_path(dir_url.to_file_path().unwrap()), - }; - - // At this point we already checked if the task name exists in package.json. - // We can therefore check for "pre" and "post" scripts too, since we're only - // dealing with package.json here and not deno.json - let task_names = vec![ - format!("pre{}", task_name), - task_name.clone(), - format!("post{}", task_name), - ]; - let custom_commands = task_runner::resolve_custom_commands( - npm_resolver.as_ref(), - node_resolver, - )?; - for task_name in &task_names { - if let Some(script) = scripts.get(task_name) { - let exit_code = run_task(RunTaskOptions { - task_name, - script, - cwd: &cwd, - env_vars: env_vars.clone(), - custom_commands: custom_commands.clone(), - npm_resolver: npm_resolver.as_ref(), - cli_options, - }) - .await?; - if exit_code > 0 { - return Ok(exit_code); - } - } - } +struct RunSingleOptions<'a> { + task_name: &'a str, + script: &'a str, + cwd: &'a Path, + custom_commands: HashMap>, +} - Ok(0) - } - }, - None => { - if task_flags.is_run { +struct TaskRunner<'a> { + tasks_config: WorkspaceTasksConfig, + task_flags: &'a TaskFlags, + npm_resolver: &'a dyn CliNpmResolver, + node_resolver: &'a NodeResolver, + env_vars: HashMap, + cli_options: &'a CliOptions, +} + +impl<'a> TaskRunner<'a> { + async fn run_task( + &self, + task_name: &String, + ) -> Result { + let Some((dir_url, task_or_script)) = self.tasks_config.task(task_name) + else { + if self.task_flags.is_run { return Err(anyhow!("Task not found: {}", task_name)); } + log::error!("Task not found: {}", task_name); if log::log_enabled!(log::Level::Error) { print_available_tasks( &mut std::io::stderr(), - &cli_options.start_dir, - &tasks_config, + &self.cli_options.start_dir, + &self.tasks_config, )?; } - Ok(1) + return Ok(1); + }; + + match task_or_script { + TaskOrScript::Task(_tasks, definition) => { + self.run_deno_task(dir_url, task_name, definition).await + } + TaskOrScript::Script(scripts, _script) => { + self.run_npm_script(dir_url, task_name, scripts).await + } } } -} -struct RunTaskOptions<'a> { - task_name: &'a str, - script: &'a str, - cwd: &'a Path, - env_vars: HashMap, - custom_commands: HashMap>, - npm_resolver: &'a dyn CliNpmResolver, - cli_options: &'a CliOptions, -} + async fn run_deno_task( + &self, + dir_url: &Url, + task_name: &String, + definition: &TaskDefinition, + ) -> Result { + let cwd = match &self.task_flags.cwd { + Some(path) => canonicalize_path(&PathBuf::from(path)) + .context("failed canonicalizing --cwd")?, + None => normalize_path(dir_url.to_file_path().unwrap()), + }; -async fn run_task(opts: RunTaskOptions<'_>) -> Result { - let RunTaskOptions { - task_name, - script, - cwd, - env_vars, - custom_commands, - npm_resolver, - cli_options, - } = opts; + let custom_commands = task_runner::resolve_custom_commands( + self.npm_resolver, + self.node_resolver, + )?; + self + .run_single(RunSingleOptions { + task_name, + script: &definition.command, + cwd: &cwd, + custom_commands, + }) + .await + } - output_task( - opts.task_name, - &task_runner::get_script_with_args(script, cli_options.argv()), - ); + async fn run_npm_script( + &self, + dir_url: &Url, + task_name: &String, + scripts: &IndexMap, + ) -> Result { + // ensure the npm packages are installed if using a managed resolver + if let Some(npm_resolver) = self.npm_resolver.as_managed() { + npm_resolver.ensure_top_level_package_json_install().await?; + } + + let cwd = match &self.task_flags.cwd { + Some(path) => canonicalize_path(&PathBuf::from(path))?, + None => normalize_path(dir_url.to_file_path().unwrap()), + }; + + // At this point we already checked if the task name exists in package.json. + // We can therefore check for "pre" and "post" scripts too, since we're only + // dealing with package.json here and not deno.json + let task_names = vec![ + format!("pre{}", task_name), + task_name.clone(), + format!("post{}", task_name), + ]; + let custom_commands = task_runner::resolve_custom_commands( + self.npm_resolver, + self.node_resolver, + )?; + for task_name in &task_names { + if let Some(script) = scripts.get(task_name) { + let exit_code = self + .run_single(RunSingleOptions { + task_name, + script, + cwd: &cwd, + custom_commands: custom_commands.clone(), + }) + .await?; + if exit_code > 0 { + return Ok(exit_code); + } + } + } + + Ok(0) + } - Ok( - task_runner::run_task(task_runner::RunTaskOptions { + async fn run_single( + &self, + opts: RunSingleOptions<'_>, + ) -> Result { + let RunSingleOptions { task_name, script, cwd, - env_vars, custom_commands, - init_cwd: opts.cli_options.initial_cwd(), - argv: cli_options.argv(), - root_node_modules_dir: npm_resolver.root_node_modules_path(), - stdio: None, - }) - .await? - .exit_code, - ) + } = opts; + + output_task( + opts.task_name, + &task_runner::get_script_with_args(script, self.cli_options.argv()), + ); + + Ok( + task_runner::run_task(task_runner::RunTaskOptions { + task_name, + script, + cwd, + env_vars: self.env_vars.clone(), + custom_commands, + init_cwd: self.cli_options.initial_cwd(), + argv: self.cli_options.argv(), + root_node_modules_dir: self.npm_resolver.root_node_modules_path(), + stdio: None, + }) + .await? + .exit_code, + ) + } } fn output_task(task_name: &str, script: &str) { @@ -222,79 +257,88 @@ fn print_available_tasks( " {}", colors::red("No tasks found in configuration file") )?; - } else { - let mut seen_task_names = - HashSet::with_capacity(tasks_config.tasks_count()); - for maybe_config in [&tasks_config.member, &tasks_config.root] { - let Some(config) = maybe_config else { - continue; - }; - for (is_root, is_deno, (key, task)) in config - .deno_json - .as_ref() - .map(|config| { - let is_root = !is_cwd_root_dir - && config.folder_url - == *workspace_dir.workspace.root_dir().as_ref(); - config - .tasks - .iter() - .map(move |(k, t)| (is_root, true, (k, Cow::Borrowed(t)))) - }) - .into_iter() - .flatten() - .chain( - config - .package_json - .as_ref() - .map(|config| { - let is_root = !is_cwd_root_dir - && config.folder_url - == *workspace_dir.workspace.root_dir().as_ref(); - config.tasks.iter().map(move |(k, v)| { - (is_root, false, (k, Cow::Owned(Task::Definition(v.clone())))) - }) - }) - .into_iter() - .flatten(), - ) - { - if !seen_task_names.insert(key) { + return Ok(()); + } + + struct AvailableTaskDescription { + is_root: bool, + is_deno: bool, + name: String, + task: TaskDefinition, + } + let mut seen_task_names = HashSet::with_capacity(tasks_config.tasks_count()); + let mut task_descriptions = Vec::with_capacity(tasks_config.tasks_count()); + + for maybe_config in [&tasks_config.member, &tasks_config.root] { + let Some(config) = maybe_config else { + continue; + }; + + if let Some(config) = config.deno_json.as_ref() { + let is_root = !is_cwd_root_dir + && config.folder_url == *workspace_dir.workspace.root_dir().as_ref(); + + for (name, definition) in &config.tasks { + if !seen_task_names.insert(name) { continue; // already seen } - writeln!( - writer, - "- {}{}", - colors::cyan(key), - if is_root { - if is_deno { - format!(" {}", colors::italic_gray("(workspace)")) - } else { - format!(" {}", colors::italic_gray("(workspace package.json)")) - } - } else if is_deno { - "".to_string() - } else { - format!(" {}", colors::italic_gray("(package.json)")) - } - )?; - let definition = match task.as_ref() { - Task::Definition(definition) => definition, - Task::Commented { definition, .. } => definition, - }; - if let Task::Commented { comments, .. } = task.as_ref() { - let slash_slash = colors::italic_gray("//"); - for comment in comments { - writeln!( - writer, - " {slash_slash} {}", - colors::italic_gray(comment) - )?; - } + task_descriptions.push(AvailableTaskDescription { + is_root, + is_deno: true, + name: name.to_string(), + task: definition.clone(), + }); + } + } + + if let Some(config) = config.package_json.as_ref() { + let is_root = !is_cwd_root_dir + && config.folder_url == *workspace_dir.workspace.root_dir().as_ref(); + for (name, script) in &config.tasks { + if !seen_task_names.insert(name) { + continue; // already seen } - writeln!(writer, " {definition}")?; + + task_descriptions.push(AvailableTaskDescription { + is_root, + is_deno: false, + name: name.to_string(), + task: deno_config::deno_json::TaskDefinition { + command: script.to_string(), + dependencies: vec![], + description: None, + }, + }); + } + } + } + + for desc in task_descriptions { + writeln!( + writer, + "- {}{}", + colors::cyan(desc.name), + if desc.is_root { + if desc.is_deno { + format!(" {}", colors::italic_gray("(workspace)")) + } else { + format!(" {}", colors::italic_gray("(workspace package.json)")) + } + } else if desc.is_deno { + "".to_string() + } else { + format!(" {}", colors::italic_gray("(package.json)")) } + )?; + if let Some(description) = &desc.task.description { + let slash_slash = colors::italic_gray("//"); + writeln!( + writer, + " {slash_slash} {}", + colors::italic_gray(description) + )?; } + writeln!(writer, " {}", desc.task.command)?; } Ok(()) -- cgit v1.2.3 From 99d5c6e423fe67f7f062296ffd38b38f92b7ab70 Mon Sep 17 00:00:00 2001 From: Miguel Rodrigues <64497525+mbrdg@users.noreply.github.com> Date: Sat, 16 Nov 2024 13:57:14 +0000 Subject: fix(cli): show prefix hint when installing a package globally (#26629) Closes #26545 Shows a hint when a package is installed globally, otherwise fallbacks to the existing implementation. --- cli/tools/installer.rs | 45 ++++++++++++++++++++++++++++++++++++++++++++- cli/tools/registry/mod.rs | 1 + cli/tools/registry/pm.rs | 2 +- 3 files changed, 46 insertions(+), 2 deletions(-) (limited to 'cli/tools') diff --git a/cli/tools/installer.rs b/cli/tools/installer.rs index 1655f0a32..fe477a8e6 100644 --- a/cli/tools/installer.rs +++ b/cli/tools/installer.rs @@ -3,6 +3,7 @@ use crate::args::resolve_no_prompt; use crate::args::AddFlags; use crate::args::CaData; +use crate::args::CacheSetting; use crate::args::ConfigFlag; use crate::args::Flags; use crate::args::InstallFlags; @@ -13,8 +14,11 @@ use crate::args::TypeCheckMode; use crate::args::UninstallFlags; use crate::args::UninstallKind; use crate::factory::CliFactory; +use crate::file_fetcher::FileFetcher; use crate::graph_container::ModuleGraphContainer; use crate::http_util::HttpClientProvider; +use crate::jsr::JsrFetchResolver; +use crate::npm::NpmFetchResolver; use crate::util::fs::canonicalize_path_maybe_not_exists; use deno_core::anyhow::bail; @@ -354,12 +358,51 @@ async fn install_global( ) -> Result<(), AnyError> { // ensure the module is cached let factory = CliFactory::from_flags(flags.clone()); + + let http_client = factory.http_client_provider(); + let deps_http_cache = factory.global_http_cache()?; + let mut deps_file_fetcher = FileFetcher::new( + deps_http_cache.clone(), + CacheSetting::ReloadAll, + true, + http_client.clone(), + Default::default(), + None, + ); + + let npmrc = factory.cli_options().unwrap().npmrc(); + + deps_file_fetcher.set_download_log_level(log::Level::Trace); + let deps_file_fetcher = Arc::new(deps_file_fetcher); + let jsr_resolver = Arc::new(JsrFetchResolver::new(deps_file_fetcher.clone())); + let npm_resolver = Arc::new(NpmFetchResolver::new( + deps_file_fetcher.clone(), + npmrc.clone(), + )); + + let entry_text = install_flags_global.module_url.as_str(); + let req = super::registry::AddRmPackageReq::parse(entry_text); + + // found a package requirement but missing the prefix + if let Ok(Err(package_req)) = req { + if jsr_resolver.req_to_nv(&package_req).await.is_some() { + bail!( + "{entry_text} is missing a prefix. Did you mean `{}`?", + crate::colors::yellow(format!("deno install -g jsr:{package_req}")) + ); + } else if npm_resolver.req_to_nv(&package_req).await.is_some() { + bail!( + "{entry_text} is missing a prefix. Did you mean `{}`?", + crate::colors::yellow(format!("deno install -g npm:{package_req}")) + ); + } + } + factory .main_module_graph_container() .await? .load_and_type_check_files(&[install_flags_global.module_url.clone()]) .await?; - let http_client = factory.http_client_provider(); // create the install shim create_install_shim(http_client, &flags, install_flags_global).await diff --git a/cli/tools/registry/mod.rs b/cli/tools/registry/mod.rs index 4098d62e3..12289c581 100644 --- a/cli/tools/registry/mod.rs +++ b/cli/tools/registry/mod.rs @@ -69,6 +69,7 @@ pub use pm::add; pub use pm::cache_top_level_deps; pub use pm::remove; pub use pm::AddCommandName; +pub use pm::AddRmPackageReq; use publish_order::PublishOrderGraph; use unfurl::SpecifierUnfurler; diff --git a/cli/tools/registry/pm.rs b/cli/tools/registry/pm.rs index 68913e259..c1ea2c75e 100644 --- a/cli/tools/registry/pm.rs +++ b/cli/tools/registry/pm.rs @@ -679,7 +679,7 @@ enum AddRmPackageReqValue { } #[derive(Debug, PartialEq, Eq)] -struct AddRmPackageReq { +pub struct AddRmPackageReq { alias: String, value: AddRmPackageReqValue, } -- cgit v1.2.3 From abf06eb87f10ebed93b39948213dccfe086bd0fa Mon Sep 17 00:00:00 2001 From: HasanAlrimawi <141642411+HasanAlrimawi@users.noreply.github.com> Date: Sat, 16 Nov 2024 16:59:31 +0200 Subject: feat(watch): log which file changed on HMR or watch change (#25801) Closes #25504 --- cli/tools/bench/mod.rs | 1 + cli/tools/fmt.rs | 1 + cli/tools/lint/mod.rs | 1 + cli/tools/run/mod.rs | 3 ++- cli/tools/serve.rs | 3 ++- cli/tools/test/mod.rs | 1 + 6 files changed, 8 insertions(+), 2 deletions(-) (limited to 'cli/tools') diff --git a/cli/tools/bench/mod.rs b/cli/tools/bench/mod.rs index 272d06335..1d49fa061 100644 --- a/cli/tools/bench/mod.rs +++ b/cli/tools/bench/mod.rs @@ -486,6 +486,7 @@ pub async fn run_benchmarks_with_watch( ), move |flags, watcher_communicator, changed_paths| { let bench_flags = bench_flags.clone(); + watcher_communicator.show_path_changed(changed_paths.clone()); Ok(async move { let factory = CliFactory::from_flags_for_watcher( flags, diff --git a/cli/tools/fmt.rs b/cli/tools/fmt.rs index 56b1632cf..2221c0da9 100644 --- a/cli/tools/fmt.rs +++ b/cli/tools/fmt.rs @@ -83,6 +83,7 @@ pub async fn format( file_watcher::PrintConfig::new("Fmt", !watch_flags.no_clear_screen), move |flags, watcher_communicator, changed_paths| { let fmt_flags = fmt_flags.clone(); + watcher_communicator.show_path_changed(changed_paths.clone()); Ok(async move { let factory = CliFactory::from_flags(flags); let cli_options = factory.cli_options()?; diff --git a/cli/tools/lint/mod.rs b/cli/tools/lint/mod.rs index 36f1cc049..a9c2d0152 100644 --- a/cli/tools/lint/mod.rs +++ b/cli/tools/lint/mod.rs @@ -80,6 +80,7 @@ pub async fn lint( file_watcher::PrintConfig::new("Lint", !watch_flags.no_clear_screen), move |flags, watcher_communicator, changed_paths| { let lint_flags = lint_flags.clone(); + watcher_communicator.show_path_changed(changed_paths.clone()); Ok(async move { let factory = CliFactory::from_flags(flags); let cli_options = factory.cli_options()?; diff --git a/cli/tools/run/mod.rs b/cli/tools/run/mod.rs index bebb3f588..8fab544ec 100644 --- a/cli/tools/run/mod.rs +++ b/cli/tools/run/mod.rs @@ -124,7 +124,8 @@ async fn run_with_watch( !watch_flags.no_clear_screen, ), WatcherRestartMode::Automatic, - move |flags, watcher_communicator, _changed_paths| { + move |flags, watcher_communicator, changed_paths| { + watcher_communicator.show_path_changed(changed_paths.clone()); Ok(async move { let factory = CliFactory::from_flags_for_watcher( flags, diff --git a/cli/tools/serve.rs b/cli/tools/serve.rs index e3f9e94f8..d7989140a 100644 --- a/cli/tools/serve.rs +++ b/cli/tools/serve.rs @@ -151,7 +151,8 @@ async fn serve_with_watch( !watch_flags.no_clear_screen, ), WatcherRestartMode::Automatic, - move |flags, watcher_communicator, _changed_paths| { + move |flags, watcher_communicator, changed_paths| { + watcher_communicator.show_path_changed(changed_paths.clone()); Ok(async move { let factory = CliFactory::from_flags_for_watcher( flags, diff --git a/cli/tools/test/mod.rs b/cli/tools/test/mod.rs index 966b0d285..6357ebcae 100644 --- a/cli/tools/test/mod.rs +++ b/cli/tools/test/mod.rs @@ -1661,6 +1661,7 @@ pub async fn run_tests_with_watch( ), move |flags, watcher_communicator, changed_paths| { let test_flags = test_flags.clone(); + watcher_communicator.show_path_changed(changed_paths.clone()); Ok(async move { let factory = CliFactory::from_flags_for_watcher( flags, -- cgit v1.2.3 From 106d47a0136c04ca219a81c3f91505116e13855e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Mon, 18 Nov 2024 22:54:28 +0000 Subject: feat: fmt and lint respect .gitignore file (#26897) Closes https://github.com/denoland/deno/issues/26573 --- cli/tools/fmt.rs | 1 + cli/tools/lint/mod.rs | 1 + 2 files changed, 2 insertions(+) (limited to 'cli/tools') diff --git a/cli/tools/fmt.rs b/cli/tools/fmt.rs index 2221c0da9..d40abd5f5 100644 --- a/cli/tools/fmt.rs +++ b/cli/tools/fmt.rs @@ -228,6 +228,7 @@ fn collect_fmt_files( }) .ignore_git_folder() .ignore_node_modules() + .use_gitignore() .set_vendor_folder(cli_options.vendor_dir_path().map(ToOwned::to_owned)) .collect_file_patterns(&deno_config::fs::RealDenoConfigFs, files) } diff --git a/cli/tools/lint/mod.rs b/cli/tools/lint/mod.rs index a9c2d0152..fcefb4587 100644 --- a/cli/tools/lint/mod.rs +++ b/cli/tools/lint/mod.rs @@ -436,6 +436,7 @@ fn collect_lint_files( }) .ignore_git_folder() .ignore_node_modules() + .use_gitignore() .set_vendor_folder(cli_options.vendor_dir_path().map(ToOwned::to_owned)) .collect_file_patterns(&deno_config::fs::RealDenoConfigFs, files) } -- cgit v1.2.3 From 661aa22c03f829489e2d9289dee0a8292a95227a Mon Sep 17 00:00:00 2001 From: David Sherret Date: Tue, 19 Nov 2024 07:45:09 -0500 Subject: feat(task): dependencies (#26467) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds support for "dependencies" in `deno task` subcommand: ```jsonc { "tasks": { "build": "deno run -RW build.ts", "generate": "deno run -RW generate.ts", "serve": { "command": "deno run -RN server.ts", "dependencies": ["build", "generate"] } } } ``` Executing `deno task serve` will first execute `build` and `generate` tasks (in parallel) and once both complete the `serve` task will be executed. Number of tasks run in parallel is equal to the no of cores on the machine, and respects `DENO_JOBS` env var if one is specified. Part of https://github.com/denoland/deno/issues/26462 --------- Co-authored-by: Bartek Iwańczuk Co-authored-by: Marvin Hagemeister --- cli/tools/task.rs | 229 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 215 insertions(+), 14 deletions(-) (limited to 'cli/tools') diff --git a/cli/tools/task.rs b/cli/tools/task.rs index a13efbaf4..682dbf814 100644 --- a/cli/tools/task.rs +++ b/cli/tools/task.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use std::collections::HashSet; +use std::num::NonZeroUsize; use std::path::Path; use std::path::PathBuf; use std::rc::Rc; @@ -15,6 +16,10 @@ use deno_core::anyhow::anyhow; use deno_core::anyhow::bail; use deno_core::anyhow::Context; use deno_core::error::AnyError; +use deno_core::futures::future::LocalBoxFuture; +use deno_core::futures::stream::futures_unordered; +use deno_core::futures::FutureExt; +use deno_core::futures::StreamExt; use deno_core::url::Url; use deno_path_util::normalize_path; use deno_runtime::deno_node::NodeResolver; @@ -68,6 +73,13 @@ pub async fn execute_script( let node_resolver = factory.node_resolver().await?; let env_vars = task_runner::real_env_vars(); + let no_of_concurrent_tasks = if let Ok(value) = std::env::var("DENO_JOBS") { + value.parse::().ok() + } else { + std::thread::available_parallelism().ok() + } + .unwrap_or_else(|| NonZeroUsize::new(2).unwrap()); + let task_runner = TaskRunner { tasks_config, task_flags: &task_flags, @@ -75,7 +87,9 @@ pub async fn execute_script( node_resolver: node_resolver.as_ref(), env_vars, cli_options, + concurrency: no_of_concurrent_tasks.into(), }; + task_runner.run_task(task_name).await } @@ -93,30 +107,156 @@ struct TaskRunner<'a> { node_resolver: &'a NodeResolver, env_vars: HashMap, cli_options: &'a CliOptions, + concurrency: usize, } impl<'a> TaskRunner<'a> { - async fn run_task( + pub async fn run_task( &self, - task_name: &String, + task_name: &str, ) -> Result { - let Some((dir_url, task_or_script)) = self.tasks_config.task(task_name) - else { - if self.task_flags.is_run { - return Err(anyhow!("Task not found: {}", task_name)); + match sort_tasks_topo(task_name, &self.tasks_config) { + Ok(sorted) => self.run_tasks_in_parallel(sorted).await, + Err(err) => match err { + TaskError::NotFound(name) => { + if self.task_flags.is_run { + return Err(anyhow!("Task not found: {}", name)); + } + + log::error!("Task not found: {}", name); + if log::log_enabled!(log::Level::Error) { + self.print_available_tasks()?; + } + Ok(1) + } + TaskError::TaskDepCycle { path } => { + log::error!("Task cycle detected: {}", path.join(" -> ")); + Ok(1) + } + }, + } + } + + pub fn print_available_tasks(&self) -> Result<(), std::io::Error> { + print_available_tasks( + &mut std::io::stderr(), + &self.cli_options.start_dir, + &self.tasks_config, + ) + } + + async fn run_tasks_in_parallel( + &self, + task_names: Vec, + ) -> Result { + struct PendingTasksContext { + completed: HashSet, + running: HashSet, + task_names: Vec, + } + + impl PendingTasksContext { + fn has_remaining_tasks(&self) -> bool { + self.completed.len() < self.task_names.len() } - log::error!("Task not found: {}", task_name); - if log::log_enabled!(log::Level::Error) { - print_available_tasks( - &mut std::io::stderr(), - &self.cli_options.start_dir, - &self.tasks_config, - )?; + fn mark_complete(&mut self, task_name: String) { + self.running.remove(&task_name); + self.completed.insert(task_name); } - return Ok(1); + + fn get_next_task<'a>( + &mut self, + runner: &'a TaskRunner<'a>, + ) -> Option>> { + for name in &self.task_names { + if self.completed.contains(name) || self.running.contains(name) { + continue; + } + + let should_run = if let Ok((_, def)) = runner.get_task(name) { + match def { + TaskOrScript::Task(_, def) => def + .dependencies + .iter() + .all(|dep| self.completed.contains(dep)), + TaskOrScript::Script(_, _) => true, + } + } else { + false + }; + + if !should_run { + continue; + } + + self.running.insert(name.clone()); + let name = name.clone(); + return Some( + async move { + runner + .run_task_no_dependencies(&name) + .await + .map(|exit_code| (exit_code, name)) + } + .boxed_local(), + ); + } + None + } + } + + let mut context = PendingTasksContext { + completed: HashSet::with_capacity(task_names.len()), + running: HashSet::with_capacity(self.concurrency), + task_names, + }; + + let mut queue = futures_unordered::FuturesUnordered::new(); + + while context.has_remaining_tasks() { + while queue.len() < self.concurrency { + if let Some(task) = context.get_next_task(self) { + queue.push(task); + } else { + break; + } + } + + // If queue is empty at this point, then there are no more tasks in the queue. + let Some(result) = queue.next().await else { + debug_assert_eq!(context.task_names.len(), 0); + break; + }; + + let (exit_code, name) = result?; + if exit_code > 0 { + return Ok(exit_code); + } + + context.mark_complete(name); + } + + Ok(0) + } + + fn get_task( + &self, + task_name: &str, + ) -> Result<(&Url, TaskOrScript), TaskError> { + let Some(result) = self.tasks_config.task(task_name) else { + return Err(TaskError::NotFound(task_name.to_string())); }; + Ok(result) + } + + async fn run_task_no_dependencies( + &self, + task_name: &String, + ) -> Result { + let (dir_url, task_or_script) = self.get_task(task_name.as_str()).unwrap(); + match task_or_script { TaskOrScript::Task(_tasks, definition) => { self.run_deno_task(dir_url, task_name, definition).await @@ -234,6 +374,59 @@ impl<'a> TaskRunner<'a> { } } +#[derive(Debug)] +enum TaskError { + NotFound(String), + TaskDepCycle { path: Vec }, +} + +fn sort_tasks_topo( + name: &str, + task_config: &WorkspaceTasksConfig, +) -> Result, TaskError> { + fn sort_visit<'a>( + name: &'a str, + sorted: &mut Vec, + mut path: Vec<&'a str>, + tasks_config: &'a WorkspaceTasksConfig, + ) -> Result<(), TaskError> { + // Already sorted + if sorted.iter().any(|sorted_name| sorted_name == name) { + return Ok(()); + } + + // Graph has a cycle + if path.contains(&name) { + path.push(name); + return Err(TaskError::TaskDepCycle { + path: path.iter().map(|s| s.to_string()).collect(), + }); + } + + let Some(def) = tasks_config.task(name) else { + return Err(TaskError::NotFound(name.to_string())); + }; + + if let TaskOrScript::Task(_, actual_def) = def.1 { + for dep in &actual_def.dependencies { + let mut path = path.clone(); + path.push(name); + sort_visit(dep, sorted, path, tasks_config)? + } + } + + sorted.push(name.to_string()); + + Ok(()) + } + + let mut sorted: Vec = vec![]; + + sort_visit(name, &mut sorted, Vec::new(), task_config)?; + + Ok(sorted) +} + fn output_task(task_name: &str, script: &str) { log::info!( "{} {} {}", @@ -339,6 +532,14 @@ fn print_available_tasks( )?; } writeln!(writer, " {}", desc.task.command)?; + if !desc.task.dependencies.is_empty() { + writeln!( + writer, + " {} {}", + colors::gray("depends on:"), + colors::cyan(desc.task.dependencies.join(", ")) + )?; + } } Ok(()) -- cgit v1.2.3